In the quick start guide for the ArcGIS javascript api, it has the following sample code:
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<title>ArcGIS API for JavaScript Hello World App</title>
<style>
html, body, #viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.12/esri/css/main.css">
<script src="https://js.arcgis.com/4.12/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView"
], function(Map, MapView) {
var map = new Map({
basemap: "topo-vector"
});
var view = new MapView({
container: "viewDiv",
map: map,
center: [-118.71511,34.09042],
zoom: 11
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
Which works great for a simple web page. However, I'm using Blazor (server-side) and I would like to encapsulate the (above) code into a Blazor component. So I hit my first stumbling block - I'm not allowed to add a <script>
tag inside a Blazor component. That's because the control could be created dynamically at any time. So I thought I'd address the issue with Stimulus instead.
Here's my Blazor component (so far). I have a file called Map.razor
:
<div data-controller="map"></div>
@code {
protected override bool ShouldRender()
{
var allowRefresh = false;
return allowRefresh;
}
}
I added the ShouldRender
method so that the component will be rendered only once (when it's added). And here's what I'm trying to achieve in my Stimulus controller map-controller.js
:
import { Controller } from "stimulus";
import EsriMap from "esri/Map";
import MapView from "esri/views/MapView";
export default class extends Controller {
connect() {
var map = new EsriMap({
basemap: "topo-vector"
});
var view = new MapView({
container: this.element,
map: map,
center: [-118.80500, 34.02700], // longitude, latitude
zoom: 13
});
}
}
Originally, I tried adding ArcGIS so that it would be built using Webpack (so that the above code would work). However, I came across a compatibility issue between the ArcGIS javascript api and tailwindcss. ArcGIS would not compile because there was an issue with a call to require('fs')
.
Rather than workaround the require('fs')
issue (which is outside my existing experience), I opted to pull in the ArcGIS js via the CDN. So I tried to setup ArcGIS using the external
config feature in Webpack. Here's my webpack.js.config
file:
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const bundleFileName = 'holly';
const dirName = 'Holly/wwwroot/dist';
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
externals: {
'esrimap': 'esri/Map',
'mapview': 'esri/views/MapView'
},
entry: ['./Holly/wwwroot/js/app.js', './Holly/wwwroot/css/styles.css'],
output: {
filename: bundleFileName + '.js',
path: path.resolve(__dirname, dirName),
libraryTarget: "umd"
},
module: {
rules: [{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: bundleFileName + '.css'
})
]
};
};
And this is what I did in my stimulus controller:
import { Controller } from "stimulus";
import EsriMap from "esrimap";
import MapView from "mapview";
export default class extends Controller {
connect() {
var map = new EsriMap({
basemap: "topo-vector"
});
var view = new MapView({
container: this.element,
map: map,
center: [-118.80500, 34.02700], // longitude, latitude
zoom: 13
});
}
}
However, I see the following exception in the browser:
TypeError: esrimap__WEBPACK_IMPORTED_MODULE_1___default.a is not a constructor
at Controller.connect (map_controller.js:14)
at Context.connect (context.ts:35)
at Module.connectContextForScope (module.ts:40)
at eval (router.ts:109)
at Array.forEach (<anonymous>)
at Router.connectModule (router.ts:109)
at Router.loadDefinition (router.ts:60)
at eval (application.ts:51)
at Array.forEach (<anonymous>)
at Application.load (application.ts:51)
As you might have guessed, I'm hitting the limit of my javascript/webpack knowledge. I did do a little research on the ArcGIS javascript API and whether it supports commonjs. Apparently it uses Dojo, which supports AMD. So I tried the following config instead:
externals: [{
'esrimap': {
commonjs: 'esri/Map',
commonjs2: 'esri/Map',
amd: 'esri/Map'
},
'mapview': {
commonjs: 'esri/views/MapView',
commonjs2: 'esri/views/MapView',
amd: 'esri/views/MapView'
}
}],
But I get the same error. I've read through the webpack documentation - it's not clear to me how I should configure this. Am I doing something fundamentally wrong?
So I thought I'd address the issue with Stimulus instead.
The following is an answer to the question preceding the phrase above, ignoring everything that follows. An alternative solution that should work even though it's not an answer to your complete question.
Create a script dedicated to your blazor component, and render it or reference it in your Pages/_host.cshtml
or wwwroot/index.html
:
<script>
window.myComponent = {
init: function(options) {
require([
"esri/Map",
"esri/views/MapView"
], function(Map, MapView) {
var map = new Map({
basemap: "topo-vector"
});
var view = new MapView({
container: options.containerId,
map: map,
center: [-118.71511,34.09042],
zoom: 11
});
}); // end require
} // end init
}; // end myComponent
</script>
And call this script in you component by overriding async Task OnAfterRenderAsync(bool isFirstRender)
. Only call it if isFirstRender
is set to true
.
You can call the script by injecting IJSRuntime and calling await InvokeAsync("myComponent.init", "container-id")
Something like this (untested)
@inject IJSRuntime JSRuntime
<div id="@containerId" data-controller="map"></div>
@code {
private string containerId = Guid.CreateGuid().ToString("n");
protected override bool ShouldRender()
{
var allowRefresh = false;
return allowRefresh;
}
protected override async Task OnAfterRenderAsync(bool isFirstRender) {
if (isFirstRender){
await JSRuntime.InvokeAsync("myComponent.init", new { containerId: containerId })
}
}
}