/**
*
* The Map field derives from [Field](kiss.ui.Field.html).
*
* **Map** field displays a map with a text field to enter an address or geo coordinates.
*
* @param {object} config
* @param {string} [config.value] - Default address or geo coordinates like: latitude,longitude
* @param {number} [config.zoom] - Zoom level (default 10, max 19)
* @param {number} [config.mapHeight] - Height (the map width is defined by the field's width)
* @param {number|string} [config.mapRatio] - Ratio between the field width and the map height (default 4/3). Can be a number or a string to evaluate, like: "4/3", "16/9", 1.77, 1.33, 2, etc. Use this property only if the height is not defined.
* @returns this
*
* ## Generated markup
* ```
* <a-mapfield class="a-mapfield">
* <label class="field-label"></label>
* <input type="text|number|date" class="field-input"></input>
* </a-field>
* ```
*/
kiss.ux.MapField = class MapField extends kiss.ui.Field {
/**
* Its a Custom Web Component. Do not use the constructor directly with the **new** keyword.
* Instead, use one of the 3 following methods:
*
* Create the Web Component and call its **init** method:
* ```
* const myMapField = document.createElement("a-mapfield").init(config)
* ```
*
* Or use a shorthand to create one the various field types:
* ```
* const myMapField = createMapField({
* value: "-21,55",
* zoom: 15,
* width: 600,
* mapHeight: 400
* })
*
* myMapField.render()
* ```
*
* Or directly declare the config inside a container component:
* ```
* const myPanel = createPanel({
* title: "My panel",
* items: [
* {
* type: "mapfield",
* value: "-21,55",
* zoom: 15,
* width: 600,
* mapHeight: 400
* }
* ]
* })
* myPanel.render()
* ```
*/
constructor() {
super()
}
/**
* @ignore
*/
init(config = {}) {
config.type = "mapField"
config.autoSize = true
// Generates the text field to enter the address or geo coordinates
super.init(config)
// Ensure the map will be displayed below the field
this.style.flexFlow = "row wrap"
this._observeKeys()
return this
}
/**
* @ignore
*/
_afterRender() {
// Insert a map right after the field
this._createMap()
// Adjust the map height based on the field width, if no height is defined
if (this.config.mapRatio && !this.config.mapHeight) {
this._adjustMapRatio()
}
// Set the map's default position
if (this.config.value) {
this._setMapValue(this.config.value)
}
// Add a button to expand the map fullscreen
this._addExpandButton()
}
/**
* Add a map to the field
*
* @private
* @ignore
*/
_createMap() {
let zoom = this.config.zoom || 10
if (zoom > 19) zoom = 19
if (zoom < 1) zoom = 1
this.map = createMap({
zoom: this.config.zoom,
width: this.config.width,
height: this.config.mapHeight
})
this.map.style.order = 2
this.map.style.flex = "1 1 100%"
this.appendChild(this.map)
this.map.render()
}
/**
* Adjusts the map height based on the field width
*
* @private
* @ignore
*/
_adjustMapRatio() {
this.mapRatio = this.config.mapRatio
if (typeof this.mapRatio == "string") {
const mapRatio = eval(this.mapRatio)
this.mapRatio = (isNaN(mapRatio)) ? (4 / 3) : mapRatio
}
setTimeout(() => {
const width = this.getBoundingClientRect().width
this.map.setHeight(width * 1 / this.mapRatio + "px")
}, 50)
}
/**
* Updates the field value internally
*
* @private
* @ignore
* @param {*} updates
*/
_updateField(updates) {
if (this.id in updates) {
const newValue = updates[this.id]
if (newValue || (newValue === 0) || (newValue === "")) {
this.field.value = newValue
this._setMapValue(newValue)
}
}
}
/**
* Add a button to expand the map fullscreen
*
* @private
* @ignore
*/
_addExpandButton() {
setTimeout(() => {
const fieldMap = this.map
const mapExpandButton = document.createElement("button")
mapExpandButton.innerHTML = "⛶"
mapExpandButton.classList.add("a-mapfield-button")
fieldMap.map.getViewport().appendChild(mapExpandButton)
mapExpandButton.onclick = () => this.expandMap()
}, 500)
}
/**
* @ignore
*/
_observeKeys() {
const _this = this
this.field.onkeydown = function (e) {
if (e.key === "Enter") {
_this._setMapValue(_this.field.value)
}
}
}
/**
* @ignore
*/
_setMapValue(input) {
const geoloc = kiss.tools.isGeolocation(input)
if (geoloc) {
this.map.setGeolocation(geoloc)
} else {
this.map.setAddress(input)
}
}
/**
* Expand the map fullscreen
*
* @returns this
*/
expandMap() {
let map = createMap({
width: "100%",
height: "100%",
longitude: this.map.longitude,
latitude: this.map.latitude,
zoom: this.map.zoom
})
createPanel({
title: this.config.label,
closable: true,
position: "absolute",
top: 0,
left: 0,
padding: 0,
width: "100%",
height: "100%",
items: [
map
]
}).render()
return this
}
/**
* Set a new address on the map
*
* IMPORTANT: this methods uses Nominatim for geocoding, which is a free service but has limitations when it comes to the accuracy address street number.
*
* @param {string} address
* @returns this
*
* @example
* myMapField.setAddress("10 Downing Street, London")
*/
setAddress(address) {
this.map.setAddress(address)
}
/**
* Set a new geolocation on the map
*
* @param {object} geoloc
* @param {number} geoloc.longitude
* @param {number} geoloc.latitude
* @returns this
*
* @example
* myMapField.setGeolocation({
* longitude: 2.3483915,
* latitude: 48.8534951
* })
*/
setGeolocation(geoloc) {
this.map.setGeolocation(geoloc)
}
}
// Create a Custom Element
customElements.define("a-mapfield", kiss.ux.MapField)
const createMapField = (config) => document.createElement("a-mapfield").init(config)
;
Source