Advanced usage
Integrating Kano
To avoid the burden of developing a completely new application for every mapping needs you might have, Kano provides you with the capabilities to be integrated in your web application as an <iframe/>
like this:
This iframe offers an API so that you can dynamically control the behaviour and the content of Kano, as well as how the embedding application reacts in real-time to changes in Kano, a.k.a micro frontend. You can read more about the underlying concepts in this article.
The API is a subset of the internal Kano components and uses post-robot to
- select which is the target component
- event name =
map
for 2D map andglobe
for 3D globe
- event name =
- transform external method calls to internal calls using the following event payload
- the
command
property is the mixin method name (e.g.isLayerVisible
) - the
args
property is the expected method arguments (e.g. a string, an object or an array when multiple arguments are required)
- the
- retrieve internal method call result externally
- event response
data
is the method result object
- event response
- retrieve internal property externally
- the
property
property is the internal property name (e.g.layers
) - event response
data
is the returned property value
- the
TIP
Event messaging using post-robot is always async because it relies on the postMessage API under-the-hood.
WARNING
In-memory data exchange is Json and more specifically GeoJson for map features. Do not try to inject functions or "complex" objects (e.g. class instances) in event payloads.
WARNING
You must use the same version of the post-robot library as the one used by Kano. For now, Kano relies on the 10.0.42
version of post-robot.
In addition to the commands used to access mixin methods there are a couple of dedicated commands listened by Kano to:
setLocalStorage
set key/value pairs (provided as event data payload) in its local storage, typically useful to inject access tokenssetConfiguration
set key/value pairs to override its default configuration, typically useful to configure application name, available components or actions
The following keys can be set in local storage to alter the application behaviour:
appName-jwt
to skip the login screen by injecting an authentication tokenappName-welcome
asfalse
to avoid displaying the welcome screen on first loginappName-install
asfalse
to avoid displaying the PWA installation screenappName-disconnect-dialog
asfalse
to avoid displaying the disconnection screen when server connection is lostappName-reconnect-dialog
asfalse
to avoid displaying the reconnection screen when server connection is restaured
Here is a simple code sample:
<script src="https://cdn.jsdelivr.net/npm/post-robot@10.0.10/dist/post-robot.min.js"></script>
<iframe id="kano" title="Kano" allow="geolocation *" style="width: 1024px; height: 768px;" src="kano.kalisio.com">
<script>
var kano = document.getElementById('kano').contentWindow
// Wait for Kano to be initialized
postRobot.on('kano-ready', function() {
// Optionnaly overrides default setup of Kano
postRobot.send(kano, 'setConfiguration', { 'appName': 'xxx' })
.then(function() {
// Optionnaly set a valid token to avoid authentication
return postRobot.send(kano, 'setLocalStorage', { 'xxx-jwt': 'yyy' })
})
.then(function() {
// Show and zoom to a layer
return postRobot.send(kano, 'map', { command: 'showLayer', args: 'Layer name' })
})
.then(function() {
return postRobot.send(kano, 'map', { command: 'zoomToLayer', args: 'Layer name' })
})
.then(function() {
return postRobot.send(kano, 'map', { property: 'layers' })
})
.then(function(result) {
console.log('Layer list', result.data)
})
})
</script>
A full sample exploring the different ways to interact with the API is provided here. When running the demo you can dynamically call API methods when toggling the different buttons on the left.
WARNING
Depending on the configuration of your Kano instance some features might not work as expected in the sample as it relies on some specific layers to exist.
Listening to events
There are a lot of events to be listened by integrating application to be aware of Kano internal states or user behaviour.
WARNING
You should add a listener for each event in your application, even if you don't need to do any processing, otherwise the post-robot library will raise a warning.
Frontend events
The following ones are related to Kano states:
kano-ready
when the Kano application has been initialized in the iframe so that you can safely use the iframe APIapi-ready
when the Kano backend connection has been initialized in the iframe so that you can safely call the backend APIkano-login
when the user has been authenticated in the Kano applicationkano-logout
when the user has been unauthenticated in the Kano applicationkano-disconnected
when the Kano application has been disconnected from the websocketkano-reconnected
when the Kano application has been reconnected to the websocketmap-ready
when the 2D map component has been initialized in the Kano application so that you can safely use the underlying APImap-destroyed
when the 2D map component has been destroyed in the Kano application before switching to another routeglobe-ready
when the 3D globe component has been initialized in the Kano application so that you can safely use the underlying APIglobe-destroyed
when the 3D globe component has been destroyed in the Kano application before switching to another route
The following ones are related to layers management:
layer-add
whenever a new layer will be added to the 2D/3D maplayer-added
whenever a new layer has been added to the 2D/3D map (from the internal catalog or externally)layer-removed
whenever a layer has been removed from the 2D/3D maplayer-shown
whenever a layer has been shown in the 2D/3D maplayer-hidden
whenever a new layer has been hidden in the 2D/3D maplayer-update
whenever a real-time GeoJson layer will be updated in the 2D/3D maplayer-updated
whenever a real-time GeoJson layer has been updated in the 2D/3D map
The layer-add
and layer-update
events are particular as it might expect a response, in this case the altered data will be taken into account instead of the original data when updating the layer:
postRobot.on('layer-add', (event) => {
const layer = event.data
if (layer.name === 'MyLayer') {
// Update the layer, eg change the data filter
Object.assign(layer.baseQuery, { 'properties.user': 'MyUser' })
return layer
}
})
postRobot.on('layer-update', (event) => {
const { name, geoJson } = event.data
if (name === 'MyLayer') {
const features = geoJson.features || [geoJson]
return {
type: 'FeatureCollection',
features: features.map(feature => {
// Update the features
...
})
}
}
})
The following ones are related to user interaction (mouse or gesture):
click
whenever map or a feature from a layer has been clicked (left button or tapped) in the 2D/3D map,dbclick
whenever map or a feature from a layer has been double-clicked (left button or tapped) in the 2D/3D map,contextmenu
whenever map or a feature from a layer has been right-clicked (or long tapped) in the 2D map,mousedown
whenever the user pushes the mouse button on the 2D map,mouseup
whenever the user releases the mouse button on the 2D map,mouseover
whenever the mouse enters the map or a feature from a layer in the 2D map,mouseout
whenever the mouse leaves the map or a feature from a layer in the 2D map,mousemove
whenever the mouse moves on the 2D map,dragstart
whenever the user starts dragging a 2D marker,dragend
whenever the user stops dragging a 2D marker,drag
while the user drags a 2D marker.
TIP
A feature can be tagged as draggable
by specifying it in style:
{
type: 'Feature',
…
style: { draggable: true }
}
Most user interaction events will provide you with the following properties as data payload:
longitude
andlatitude
coordinates of the interaction,feature
andlayer
(descriptor) when the target element is a feature from a layer,containerPoint
withx
andy
coordinates of the point where the interaction occurred relative to the map сontainer,layerPoint
withx
andy
coordinates of the point where the interaction occurred relative to the map layer.
By default only click
, dbclick
and contextmenu
events are sent and you should enable more (respectively disable), using the allowForwardEvents
(respectively disallowForwardEvents
) configuration option:
postRobot.send(kano, 'setConfiguration', {
// Allow more events to be emitted
'mapActivity.allowForwardEvents': ['mouseover', 'mouseout', 'mousemove', 'contextmenu']
// Do not receive these events
'mapActivity.disallowForwardEvents': ['mousemove']
})
// React to right-click (similar for others events like mouseover, mouseout, mousemove)
postRobot.on('contextmenu', (event) => {
const { latitude, longitude, feature, layer, containerPoint } = event.data
const { x, y } = containerPoint
if (feature) {
// The event targets a feature from a layer
} else {
// Otherwise the event targets the map background
}
})
The following events are related to map state changes and do not provide additional properties like interaction events:
movestart
whenever the view of the 2D map starts changing (e.g. user starts dragging the map),moveend
whenever the center of the 2D map stops changing (e.g. user stopped dragging the map),move
during any movement of the 2D map (including pan and fly animations),zoomstart
whenever the 2D map zoom is about to change (e.g. before zoom animation),zoomend
whenever the 2D map zoom changed (after any animations),zoom
during any change in zoom level, including zoom and fly animations,rotate
whenever the map bearing is changed (will provide an additionalbearing
property as data payload).
TIP
A feature can be tagged to stop events propagation, either immediate or not, by specifying it in style:
{
type: 'Feature',
…
style: { stopImmediatePropagation: ['mousedown'] }
}
Backend events
Backend service events can be listened by integrating application, in this case the serviceEvent
property, respectively the data
property, contains the service event name, respectively service event data, in the post-robot event payload:
catalog
whenever a service event is emitted on thecatalog
servicefeatures
whenever a service event is emitted on thefeatures
service
For instance, you can listen to changes in the catalog service like this:
postRobot.on('catalog', (event) => {
const { serviceEvent, data } = event.data
console.log(`Received ${serviceEvent} catalog event`)
})
Kano also provides you with an internal event bus service called events
that can be used to dispatch custom events to all connected clients. This service internally has only a create
method to send events, you can send a custom event named item-selected
through this service like this:
// Tell others clients selection changed
await postRobot.send(kano, 'event', {
name: 'item-selected', data: { id: item.id }
})
Others clients can listen to this custom event like this:
// Listen to selection change
postRobot.on('item-selected', (event) => {
const { id } = event.data
...
})
Accessing the underlying API
You can access the backend API using either the Feathers client or raw HTTP REST requests. However, in order to ease integration you can also access the backend API through the iframe API. For this simply target the api
component using post-robot, which transform external method calls to internal calls using the following event payload:
- the
service
property is the target service name (e.g.catalog
) - the
operation
property is the target service operation name (amongget
,find
,update
,patch
,remove
) - the
args
property is the expected service operation arguments
Event response data
is the method result object. In addition to the event used to access service operations the api-ready
event is to be listened by integrating application to know when the Kano backend API has been initialized in the iframe so that you can safely use it.
Here is a simple code sample:
<script src="https://cdn.jsdelivr.net/npm/post-robot@10.0.10/dist/post-robot.min.js"></script>
<iframe id="kano" title="Kano" allow="geolocation *" style="width: 1024px; height: 768px;" src="kano.kalisio.com">
<script>
var kano = document.getElementById('kano').contentWindow
// Wait for map to be initialized
postRobot.on('map-ready', () => {
// Request saved user contexts and activate the first one if any
postRobot.send(kano, 'api', { service: 'catalog', operation: 'find', args: [{ query: { type: 'Context' } }] })
.then((result) => {
const response = result.data
if (response.total > 0) postRobot.send(kano, 'map', { command: 'loadContext', args: response.data[0] })
})
})
</script>
Client-side hooks
As you cannot directly access the underlying Feathers services from the iframe, the API allows you to setup events to be sent whenever a hook is run. Upon event reception you will get hook items as input, can alter it and send it back as output. This way the original hook items will be altered as usual in Kano.
await postRobot.send(kano, 'hooks', {
service: 'catalog',
// Emitted event names can be changed, by default it will look like eg 'catalog-after-find-hook'
hooks: { after: { find: { name: 'catalog-loaded' } } }
})
postRobot.on('catalog-loaded', (event) => {
const { items } = event.data
// Update items
items.forEach(item => {
...
})
return items
})
Developing in Kano
Kano is powered by the KDK and rely on its main abstractions. If you'd like to develop an application based on Kano or extend Kano we assume you are familiar with this technology. Indeed, Kano is based on the KDK and makes the best use of all the features offered by the provided cartographic components and services.
Add stickies
The most simple way to develop in Kano is to design and integrate your own components in the 2D or 3D activity. For this you simply have to
- Put you single-file component(s) in the
src/components
folder (e.g.MyComponent.vue
) - Update the configuration to declare your component(s) in the 2D/3D activity by adding a
local.js
in theconfig
folder like this:
module.exports = {
mapActivity: { // Can also be globeActivity
stickies: {
content: [{
id: 'my-component', position: 'left', offset: [18, 0], content: [{ component: 'MyComponent' }]
}]
}
}
}
Then build/run the application as usual.
The component file should look e.g. like this:
<template>
<div>
<q-dialog ref="myDialog">
</q-dialog>
<q-btn round color="primary" icon="edit_location" @click="showDialog">
</q-btn>
</div>
</template>
<script>
export default {
name: 'my-component',
inject: ['kMap'],
data () {
return {
...
},
methods: {
async showDialog () {
this.$refs.myDialog.show()
},
onTimeChanged () {
...
}
},
async mounted () {
// To be aware of time change
this.kMap.$on('current-time-changed', this.onTimeChanged)
...
},
beforeUnmount () {
this.kMap.$off('current-time-changed', this.onTimeChanged)
}
}
</script>
// Required translations in JSON format
<i18n>
{
"fr": {
...
}
},
"en": {
...
}
}
}
</i18n>
It's also possible to create separated i18n files if you'd like, simply put your plugin_en.json
, plugin_fr.json
, etc. files in the src/i18n
folder before building the app.
Add custom code
You can update the plugin.js
entry point in the boot
folder in order to insert your own features (functions, mixins, etc.). If you'd like to enhance the default activities provided by kano you can register your own mixins in this file by using the mixin store:
import { MixinStore } from '../mixin-store'
import myMixin from '../my-mixin.js'
MixinStore.set('my-mixin', myMixin)
Then add it to the configuration of the target activity in order to make Kano apply it automatically:
module.exports = {
mapActivity: { // Can also be globeActivity
additionalMixins: [ 'my-mixin' ],
...
}
}