Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: WASM Map onClick #17

Open
Shusshu opened this issue Jun 28, 2024 · 1 comment
Open

Feature request: WASM Map onClick #17

Shusshu opened this issue Jun 28, 2024 · 1 comment

Comments

@Shusshu
Copy link

Shusshu commented Jun 28, 2024

Hi,

I'm trying add markers on the map and having callback into the kotlin code

https://github.com/ShreyashKore/wonderous_compose/blob/main/composeApp/src/wasmJsMain/kotlin/leaflet/leaflet.kt#L27

                let marker = L.marker([lat, long]).addTo(map);
                map.on('click', (event)=> {
                    if(marker !== null){
                        map.removeLayer(marker)
                    }
                    marker = L.marker([event.latlng.lat, event.latlng.lng]).addTo(map);
                    
                    wasmExports.mapCallback(event.latlng.lat, event.latlng.lng); 
                })
@OptIn(ExperimentalJsExport::class)
@JsExport
fun mapCallback(
    lat: Double,
    long: Double,
) {
    onMapClick(lat, long) // coroutine crash in js console
}

Would you know how to get the coordinate back to kotlin code?

@oblakr24
Copy link

oblakr24 commented Jul 5, 2024

@Shusshu here is how I handled this:

  1. I aded event listeners for marker change and delte events in the map, as well as dispatched a new event upon actual map click:
fun setupMap(
    L: Leaflet,
    id: String,
    initialLat: Double,
    initialLong: Double,
    zoomLevel: Float,
    maxZoomLevel: Float,
) {
    js(
        """          
            const map = L.map(id).setView([initialLat, initialLong], zoomLevel)
            L.tileLayer(
                "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
                {
                    maxZoom : maxZoomLevel,
                    attribution : "&copy; <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>"
                }
            ).addTo(map)
            
            let currentMarker = null; // Variable to store the current marker

            function marker_change_custom(event) {
                const detail = event.detail;
                const lat = detail.lat;
                const long = detail.long;
                if (currentMarker) {
                    map.removeLayer(currentMarker);
                }
                currentMarker = L.marker([lat, long]).addTo(map)
            }
            
            function map_marker_clear_custom(event) {
                if (currentMarker) {
                    map.removeLayer(currentMarker);
                }
            }
            
            function onMapClick(e) {
                const mapDiv = document.getElementById('$MAP_DIV_ID');
                if (mapDiv) {
                     const event = new CustomEvent("$EVENT_TYPE_MAP_CLICK", {
                        detail: {
                            latitude: e.latlng.lat,
                            longitude: e.latlng.lng
                        }
                    });
                    
                    mapDiv.dispatchEvent(event);
                }
            }
            map.on('click', onMapClick);
            
            document.getElementById('$MAP_DIV_ID').addEventListener('map_marker_change', marker_change_custom);
            document.getElementById('$MAP_DIV_ID').addEventListener('map_marker_clear', map_marker_clear_custom);
        """
    )
}

  1. Outside, in the calling code, I dispatch the marker delete/change events, as well as listen to map click events:
val jsCreateLatLng: (Double, Double) -> JsAny = js("function(lat, long) { return { lat: lat, long: long }; }")

@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
@Composable
actual fun MapView(
    modifier: Modifier,
    centerPos: MapPos,
    markerPos: MapPos?,
    enableParentScroll: (enabled: Boolean) -> Unit,
    onClick: (ClickedLatLng) -> Unit,
) {
    val document = remember {
        mutableStateOf<Element?>(null)
    }

    LaunchedEffect(markerPos) {
        val doc = document.value
        if (doc != null && markerPos != null) {
            val latitude = markerPos.lat
            val longitude = markerPos.long
            val detail = jsCreateLatLng(latitude, longitude)
            val detailsInit = CustomEventInit(detail = detail)

            doc.dispatchEvent(event = CustomEvent("map_marker_change", detailsInit))
        }
        if (markerPos == null) {
            doc?.dispatchEvent(event = Event("map_marker_clear"))
        }
    }

    val zoomLevel = 10f
    HtmlView(
        modifier = modifier,
        factory = {
            createElement(MAP_DIV_ID).apply {
                addEventListener(EVENT_TYPE_MAP_CLICK) { event ->
                    val mapClickEvent = event as CustomMapClickEvent
                    onClick(
                        ClickedLatLng(
                            lat = mapClickEvent.detail.latitude,
                            long = mapClickEvent.detail.longitude,
                        )
                    )
                }
                document.value = this
                id = "map"
            }
        },
        update = {
            setupMap(
                L,
                "map",
                initialLat = centerPos.lat,
                initialLong = centerPos.long,
                zoomLevel = 260 * zoomLevel,
                maxZoomLevel = 13f
            )
        },
    )
}

const val MAP_DIV_ID = "map"
const val EVENT_TYPE_MAP_CLICK = "map_click_custom"

This is likely not ideal as I am not very experienced with Kotlin-JS interop, but it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants