222 lines
8.6 KiB
Org Mode
222 lines
8.6 KiB
Org Mode
#+TITLE: Embedding maps in your apps
|
|
#+DESCRIPTION: Example's of embeding maps into your frontend application.
|
|
|
|
* Introduction
|
|
|
|
Below you will find some simple examples of using map api's inside clojure, you can use the inline eval for some of the example where sci supports a specific library.
|
|
Install npm dependencies
|
|
|
|
|
|
** Setup for downloaded version
|
|
Install the npm dependencies for react then launch shadow via the terminal or jack in via your IDE.
|
|
#+BEGIN_SRC html :results silent :exports none :tangle readme.org
|
|
Install dependencies with.
|
|
npm install
|
|
|
|
Run the project with the below command
|
|
npx shadow-cljs server app
|
|
|
|
Alternatively jack into the project from your ide.
|
|
|
|
|
|
* Google Maps
|
|
|
|
Below is an example of using google maps, it pulls in the library directly you could alternatively use an npm dependency.
|
|
|
|
You will need to provide your own api key, the examples are blank and render a warning so paste in your google api key to make this work.
|
|
|
|
|
|
#+BEGIN_SRC html :results silent :exports none :tangle resources/public/index.html
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Clojure demos</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
App loading here
|
|
</div>
|
|
<script src="/cljs-out/main_bundle.js" type="application/javascript"></script>
|
|
</body>
|
|
</html>
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC edn :results silent :exports none :tangle deps.edn
|
|
{:paths ["src" "resources"]
|
|
:deps
|
|
{org.clojure/clojure {:mvn/version "1.10.0"}
|
|
org.clojure/clojurescript {:mvn/version "1.11.60"}
|
|
funcool/promesa {:mvn/version "11.0.671"}
|
|
reagent/reagent {:mvn/version "1.2.0"}
|
|
thheller/shadow-cljs {:mvn/version "2.24.0"}}}
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC edn :results silent :exports none :tangle shadow-cljs.edn
|
|
{:deps {:aliases [:dev]}
|
|
:dev-http {8080 ["resources/public/" "classpath:public"]}
|
|
:source ["src" "../../components"]
|
|
:builds {:app {:output-dir "resources/public/cljs-out/"
|
|
:asset-path "/cljs-out"
|
|
:target :browser
|
|
:compiler-options {:infer-externs :auto
|
|
:externs ["datascript/externs.js"]
|
|
:output-feature-set :es6}
|
|
:modules {:main_bundle {:init-fn clojure-demo.core/startup!}}
|
|
:devtools {:after-load app.main/reload!}}}}
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC json :results silent :exports none :tangle package.json
|
|
{
|
|
"dependencies": {
|
|
"react": "^18.2.0",
|
|
"react-dom": "^18.2.0"
|
|
},
|
|
"devDependencies": {
|
|
"shadow-cljs": "^2.23.3",
|
|
"webpack": "^5.74.0",
|
|
"webpack-cli": "^4.10.0"
|
|
}
|
|
}
|
|
#+END_SRC
|
|
|
|
#+BEGIN_SRC clojurescript :exports none :tangle src/clojure_demo/core.cljs
|
|
(ns clojure-demo.core
|
|
(:require
|
|
["react-dom/client" :refer [createRoot]]
|
|
[cljs.core.async :as async]
|
|
[cljs.core.async.interop :as async-in]
|
|
[promesa.core :as promesa]
|
|
[shadow.cljs.modern :refer [js-await] :as shadow]
|
|
[reagent.core :as reagent]))
|
|
#+END_SRC
|
|
|
|
|
|
** Load in the google maps script by adding it to the dom
|
|
Code pulled from google
|
|
|
|
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
|
|
(defn load-google-maps-script [api-key]
|
|
(let [script (.createElement js/document "script")]
|
|
;; copied from googles recommended way of loading google maps
|
|
(set! (.-innerHTML script) (str "(g=>{var h,a,k,p=\"The Google Maps JavaScript API\",c=\"google\",l=\"importLibrary\",q=\"__ib__\",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement(\"script\"));e.set(\"libraries\",[...r]+\"\");for(k in g)e.set(k.replace(/[A-Z]/g,t=>\"_\"+t[0].toLowerCase()),g[k]);e.set(\"callback\",c+\".maps.\"+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+\" could not load.\"));a.nonce=m.querySelector(\"script[nonce]\")?.nonce||\"\";m.head.append(a)}));d[l]?console.warn(p+\" only loads once. Ignoring:\",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
|
|
key: \"" api-key "\", v: \"weekly\"});"))
|
|
(.appendChild (.-head js/document) script)))
|
|
#+END_SRC
|
|
|
|
** Create a map using google and core async
|
|
|
|
In this example we render google maps using core async, async go blocks drop type hints so we need to pull out any hinting to functions, this is a shame as this version reduces the amount of nesting.
|
|
|
|
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
|
|
(defn google-map-core-async []
|
|
(let [map-element (reagent/atom nil)
|
|
;; pulled out of the go block to satisfy the infer warnings, the go block removes hints
|
|
get-map-obj (fn [obj] (.-Map ^js/google.maps.Map obj))
|
|
get-marker-obj (fn [obj] (.-Marker ^js/google.maps.Map obj))]
|
|
(load-google-maps-script "")
|
|
|
|
;; go blocks loose hints so may cause infer warnings
|
|
(async/go (let [gMap (get-map-obj (async-in/<p! (js/google.maps.importLibrary "maps")))
|
|
gMarker (get-marker-obj (async-in/<p! (js/google.maps.importLibrary "marker")))
|
|
map (gMap. @map-element
|
|
(clj->js {:center {:lng 131.031 :lat -25.344} :zoom 4}))]
|
|
(gMarker. (clj->js {:map map
|
|
:title "test marker 1"
|
|
:position {:lng 131.031 :lat -24.344}}) "marker 1")
|
|
(gMarker. (clj->js {:map map
|
|
:title "test marker 2"
|
|
:position {:lng 131.031 :lat -25.344}}) "marker 2")))
|
|
(fn []
|
|
[:div#google-map-async.m-4
|
|
{:style {:width "400px" :height "400px"} :ref #(reset! map-element %)}
|
|
"core async map here"])))
|
|
#+END_SRC
|
|
|
|
|
|
|
|
** Create a map using google and shadow js-await
|
|
|
|
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
|
|
(defn google-map-js-await []
|
|
(let [map-element (reagent/atom nil)]
|
|
(load-google-maps-script "")
|
|
(shadow/js-await
|
|
[js-map (js/google.maps.importLibrary "maps")]
|
|
(shadow/js-await
|
|
[js-marker (js/google.maps.importLibrary "marker")]
|
|
(when @map-element
|
|
(let [gMap (.-Map ^js/google.maps.Map js-map)
|
|
gMarker (.-Marker ^js/google.maps.Marker js-marker)
|
|
map (gMap. @map-element
|
|
(clj->js {:center {:lng 131.031 :lat -25.344} :zoom 4}))]
|
|
(gMarker. (clj->js {:map map
|
|
:title "test marker 1"
|
|
:position {:lng 131.031 :lat -24.344}}) "marker 1")
|
|
(gMarker. (clj->js {:map map
|
|
:title "test marker 2"
|
|
:position {:lng 131.031 :lat -25.344}}) "marker 2")
|
|
nil))))
|
|
|
|
(fn []
|
|
[:div#google-map-js-await.m-4
|
|
{:style {:width "400px" :height "400px"} :ref #(reset! map-element %)}
|
|
"shadow js-await map here"])))
|
|
|
|
[google-map-js-await]
|
|
#+END_SRC
|
|
|
|
** Create a map using google and promesa
|
|
|
|
An example using promesa to render a google map.
|
|
|
|
#+BEGIN_SRC clojurescript :results output :tangle src/clojure_demo/core.cljs
|
|
(defn google-map-promesa []
|
|
(let [map-element (reagent/atom nil)]
|
|
(load-google-maps-script "")
|
|
|
|
(promesa/let
|
|
[js-map (js/google.maps.importLibrary "maps")
|
|
js-marker (js/google.maps.importLibrary "marker")]
|
|
|
|
(let
|
|
[gMap (.-Map ^js/google.maps.Map js-map)
|
|
gMarker (.-Marker ^js/google.maps.Marker js-marker)
|
|
map (gMap. @map-element
|
|
(clj->js {:center {:lng 131.031 :lat -25.344} :zoom 4}))]
|
|
(gMarker. (clj->js {:map map
|
|
:title "test"
|
|
:position {:lng 131.031 :lat -24.344}}) "marker 1")
|
|
|
|
(gMarker. (clj->js {:map map
|
|
:title "test"
|
|
:position {:lng 131.031 :lat -25.344}}) "marker 1")))
|
|
|
|
|
|
(fn []
|
|
[:div#google-map-promesa.m-4
|
|
{:style {:width "400px" :height "400px"} :ref #(reset! map-element %)}
|
|
"promesa map here"])))
|
|
|
|
[google-map-promesa]
|
|
#+END_SRC
|
|
|
|
|
|
#+BEGIN_SRC clojure :exports none :tangle src/clojure_demo/core.cljs
|
|
(defn current-page []
|
|
[:div
|
|
[google-map-core-async]
|
|
[google-map-promesa]
|
|
[google-map-js-await]])
|
|
|
|
(defn mount-root-page []
|
|
;; this select the main node from the html file and injects your page content
|
|
(.render
|
|
(createRoot (.getElementById js/document "app"))
|
|
(reagent/as-element [current-page])))
|
|
|
|
(def startup! (mount-root-page))
|
|
|
|
#+END_SRC
|