Select to view content in your preferred language

4.X Migration Issue

797
9
Jump to solution
08-05-2024 08:52 AM
SteveCole
Honored Contributor

I had hoped that I could just fade into the sunset with my 3.x JS API application and not have to migrate it to 4.x but the slow & delayed pace of Experience Builder functional parity makes me feel like I'll have to migrate it. The app I have is what I would call "plain" vanilla and just uses the JS API and Dojo components available when using the API. In other words, I'm not doing any React, Typescript etc. When I developed it, I used the example in the 3.x API documentation where I created custom modules to group together my app's functionality and then load/reference them as it is outlined here under Step 4 (Load a custom module) in the Documentation.

Is this method/practice still valid with the 4.x API or should I be doing this some other way? My app breaks when it hits the Require statement in my main JS file and attempts to reference the custom module. The error thrown provides zero detail as to the issue so I'm mystified as to why it's failing at that point.

Steve

0 Kudos
1 Solution

Accepted Solutions
SteveCole
Honored Contributor

Wanted to post an update because I have found a way to push through my issues. While searching the forum for some information, I came across this post which, succinctly, laid out an example of how to incorporate your own modules along with the ESRI stuff.

The short answer is to ditch AMD and use the ESM IMPORT method rather than dojo require. There were still some fine details in order to get it to work but the linked sample helped me visualize it. First big thing is that in the HTML header your main JS file should be identified as a module rather than just JS. It went from this:

<script type="text/javascript" src="js/maing.js"></script>

to this:

<script type="module" src="js/main.js"></script>

 

Within that JS file, all the require statements went away in favor of this:

import postLoadMapFunctions from "./postLoadMapFunctions.js";
import esriConfig from "https://js.arcgis.com/4.30/@arcgis/core/config.js";
import Extent from "https://js.arcgis.com/4.30/@arcgis/core/geometry/Extent.js";
import SpatialReference from "https://js.arcgis.com/4.30/@arcgis/core/geometry/SpatialReference.js";
import esriId from "https://js.arcgis.com/4.30/@arcgis/core/identity/IdentityManager.js";

I'm still using the ESRI CDN so you'll see that the base URL to the CDN version has been inserted before the "@" character. This got things going quickly but there were still hiccups. For example, sometimes you may specify one of objects but you'll get an errror in the console like this one from reactiveUtils:

esm_module_error.jpg

The fix for this is actually quite easy. Change your import reference from this:

import reactiveUtils from "https://js.arcgis.com/4.30/@arcgis/core/core/reactiveUtils.js";

to this:

import * as reactiveUtils from "https://js.arcgis.com/4.30/@arcgis/core/core/reactiveUtils.js";

 

The layout/format of your custom modules will be different because, again, there are is no dojo require statement but mine is roughly this:

import MapView from 'https://js.arcgis.com/4.29/@arcgis/core/views/MapView.js';
import Map from 'https://js.arcgis.com/4.29/@arcgis/core/Map.js';
import GraphicsLayer from 'https://js.arcgis.com/4.29/@arcgis/core/layers/GraphicsLayer.js';
import Graphic from 'https://js.arcgis.com/4.29/@arcgis/core/Graphic.js';

export default class myModule {

    constructor(){

    }

    setView(view){
        this.view = view;
    }

    connectedCallback() {
        let goToButton = document.createElement("div");
        goToButton.setAttribute("style", "background-color:#d9dde0;padding:2px 6px;cursor:pointer");
        goToButton.textContent = "Add Graphic";
        this.shadow.appendChild(goToButton);
        goToButton.addEventListener("click", (function(){
            this.addGraphic();
        }).bind(this));
    }

    addGraphic(){
        let point = {
            type: "point",
            longitude: -71.2643,
            latitude: 42.0909
        };
        let markerSymbol = {
            type: "simple-marker",
            color: [226, 119, 40]
        };
        let graphic = new Graphic({
            geometry: point,
            symbol: markerSymbol
        });
        let graphicsLayer = new GraphicsLayer();
        graphicsLayer.add(graphic);
        this.view.map.add(graphicsLayer);
		this.view.goTo(graphic);
    }
}

From that point, you're really onto just 3.X vs 4.X API changes in your code. Anyways, hope this makes sense and helps some others.

 

 

 

View solution in original post

9 Replies
JoelBennett
MVP Regular Contributor

Yes, it's still valid, but getting everything set up is slightly more complicated than previously.  Migrating from 3.x would've been a bit easier prior to 4.20, because dojo was still packaged with the API in those days.  When I migrated our platform in the days of 4.16 - 4.18, I was able to eliminate about 94% of the references to dojo, but some of those remaining references still exist to this day (although with charting components hopefully coming out of beta in the near future, we might be ready for the final push to remove dojo entirely).  When the dojo-less 4.20 came along, I thought "oh, I'll just load dojo from the 3.x install instead", but it didn't work out that way, because the 3.x loader and the 4.x loader didn't play nice together.  To make this work, I ended up copying the dojo, dijit, and dojox folders from the 3.x install into the 4.x install like so:

sshot1.png

 

The screenshot below shows the code for loading dojo separately from the Maps SDK for Javascript&colon;

 

sshot2.png

 

For good measure, the screenshot below shows the value of the "baseURL" setting in the dojo.js file which matches the environment displayed above:

sshot3.png

 

Hope that helps...

0 Kudos
SteveCole
Honored Contributor

Thanks for chiming in. Yikes. I don't like this version of Chose Your Own adventure. My original post might have led to some confusion so let me clarify- my "modules" aren't Dojo/Digit dependent per se- I'm just using the technique to break up all my code into smaller blocks based on functionality. For example, one module might have all the pop up content generating functions, another module generates a custom report. I may use an odd dojo item (dojo.number.format) but mostly they reference to JS API items like queryTask or Geometry.

It just seems like no matter what is actually inside the module, it will choke. I've tried pared down versions and the end result is the same. Here's an ESRI JS API only version with a blank function:

String.prototype.toProperCase = function () {
    return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
};
define ([
	"esri/geometry/Extent",	
	"esri/layers/FeatureLayer",
    "esri/SpatialReference",
	"esri/tasks/BufferParameters",
    "esri/tasks/FeatureSet",    
	"esri/tasks/GeometryService",
	"esri/tasks/query",
    "esri/tasks/QueryTask"
], function (
	Extent, FeatureLayer, SpatialReference, BufferParameters, FeatureSet, GeometryService, Query, QueryTask
	) {
		return declare(null,{
			create: function () {
			
			}
		});
});		

Here's a simple module from Dojo's website:

define([
    "dojo/_base/declare",
    "dojo/dom",
    "app/dateFormatter"
], function(declare, dom, dateFormatter){
    return declare(null, {
        showDate: function(id, date){
            dom.byId(id).innerHTML = dateFormatter.format(date);
        }
    });
});

Both will error out with the same message. For the heck of it, here are the consoles from my 4.x attempt on the left and the existing 3.x app which works fine. The only difference in dojoConfig I see is that the 4.x dojoConfig specifies baseUrl whereas the 3.x dojoConfig does not.

dojoConfigOutput.jpg

Again, I'm only using this approach because that's how I did it with 3.x If there's a different (and suggested) way of dividing up / compartmentalizing your code with 4.x, I'm all ears.

0 Kudos
JoelBennett
MVP Regular Contributor

I see...one of the main problems here is you won't be able to use "declare" anymore.  After migration, my modules use one of two interchangeable approaches: a vanilla implementation, or extending Accessor:

Vanilla:

 

define(["esri/Color", "esri/rest/geoprocessor/execute"], function(Color, Geoprocessor) {
	var a = function() {
		function b(options) {
			//this is the constructor
		}

		b.prototype.isAvailable = function(installation) {
			//this is an example member function
		};

		b.prototype.activate = function(toolParams) {
			//this is another example member function
		};

		return b;
	}();

	a.prototype.declaredClass = "myPackage.myFolder.myModule"; //optional

	return a;
});

 

 

Extending Accessor (note, uses undocumented "Evented" class to add support for emitting and handling events with "emit" and "on" like in 3.x):

 

define(["esri/Color", "esri/core/Accessor", "esri/core/Evented", "esri/rest/geoprocessor/execute"], function (Color, Accessor, Evented, Geoprocessor) {
	return Evented.EventedMixin(Accessor).createSubclass({
		declaredClass: "myPackage.myFolder.myModule", //optional

		constructor: function(options) {
			//this is the constructor
		},

		isAvailable: function(installation) {
			//this is an example member function
		},

		activate: function(toolParams) {
			//this is another example member function
		}
	});
});

 

 

The error message in your console also indicates it's looking for dojo modules in the ESRI JSAPI, but that was removed in 4.20 (which was what my first post was about).  It also shows you're using the API from ESRI's server, but my discussion from the previous post assumes an environment where the API has been downloaded and installed on the local server.

SteveCole
Honored Contributor

Thanks, Joel. While I have read the announcements about each new 4.x version, these migration issue aspects have definitely not been on my horizon. And yes, as you commented, I am using the ESRI CDN rather than self hosting the API.

In my dojoConfig, I have tried providing a full URL path for my custom modules but it keeps appending that to the end of the core ESRI baseURL. This shouldn't be so difficult but, if I have to append them all into an 8,000 line JS file, I'll do it. 😩

[By the way, I don't actually use Declare in my code base. I didn't know about that getting dropped and it was pure bad luck that I randomly copy/pasted that from the Dojo help pages. Doh!]

0 Kudos
JoelBennett
MVP Regular Contributor

Do you have a constraint preventing you from hosting the API locally?  I would definitely recommend hosting it to anybody that can, since it gives more control and less dependencies on third parties.

0 Kudos
SteveCole
Honored Contributor

No, I don't believe I would have any issues doing so. I kinda considered downloading the API as an option to keep my 3.x app running (I know ESRI isn't pulling the CDN plug for awhile but still).

The biggest challenge seems to be finding the actual 3.x download. I keep getting in a circular loop while looking for it. Ugh. I'm too old and too close to retirement to learn NPM. 😆

0 Kudos
JoelBennett
MVP Regular Contributor

It looks like they've taken down the 3.x links.  The direct URLs still work though...for now.  Go here, copy the URL from the JSON text it outputs, and paste back into your browser.

SteveCole
Honored Contributor

THANK YOU! Your approach might be the least painful option for me so I'll see if I'm able to incorporate it.

0 Kudos
SteveCole
Honored Contributor

Wanted to post an update because I have found a way to push through my issues. While searching the forum for some information, I came across this post which, succinctly, laid out an example of how to incorporate your own modules along with the ESRI stuff.

The short answer is to ditch AMD and use the ESM IMPORT method rather than dojo require. There were still some fine details in order to get it to work but the linked sample helped me visualize it. First big thing is that in the HTML header your main JS file should be identified as a module rather than just JS. It went from this:

<script type="text/javascript" src="js/maing.js"></script>

to this:

<script type="module" src="js/main.js"></script>

 

Within that JS file, all the require statements went away in favor of this:

import postLoadMapFunctions from "./postLoadMapFunctions.js";
import esriConfig from "https://js.arcgis.com/4.30/@arcgis/core/config.js";
import Extent from "https://js.arcgis.com/4.30/@arcgis/core/geometry/Extent.js";
import SpatialReference from "https://js.arcgis.com/4.30/@arcgis/core/geometry/SpatialReference.js";
import esriId from "https://js.arcgis.com/4.30/@arcgis/core/identity/IdentityManager.js";

I'm still using the ESRI CDN so you'll see that the base URL to the CDN version has been inserted before the "@" character. This got things going quickly but there were still hiccups. For example, sometimes you may specify one of objects but you'll get an errror in the console like this one from reactiveUtils:

esm_module_error.jpg

The fix for this is actually quite easy. Change your import reference from this:

import reactiveUtils from "https://js.arcgis.com/4.30/@arcgis/core/core/reactiveUtils.js";

to this:

import * as reactiveUtils from "https://js.arcgis.com/4.30/@arcgis/core/core/reactiveUtils.js";

 

The layout/format of your custom modules will be different because, again, there are is no dojo require statement but mine is roughly this:

import MapView from 'https://js.arcgis.com/4.29/@arcgis/core/views/MapView.js';
import Map from 'https://js.arcgis.com/4.29/@arcgis/core/Map.js';
import GraphicsLayer from 'https://js.arcgis.com/4.29/@arcgis/core/layers/GraphicsLayer.js';
import Graphic from 'https://js.arcgis.com/4.29/@arcgis/core/Graphic.js';

export default class myModule {

    constructor(){

    }

    setView(view){
        this.view = view;
    }

    connectedCallback() {
        let goToButton = document.createElement("div");
        goToButton.setAttribute("style", "background-color:#d9dde0;padding:2px 6px;cursor:pointer");
        goToButton.textContent = "Add Graphic";
        this.shadow.appendChild(goToButton);
        goToButton.addEventListener("click", (function(){
            this.addGraphic();
        }).bind(this));
    }

    addGraphic(){
        let point = {
            type: "point",
            longitude: -71.2643,
            latitude: 42.0909
        };
        let markerSymbol = {
            type: "simple-marker",
            color: [226, 119, 40]
        };
        let graphic = new Graphic({
            geometry: point,
            symbol: markerSymbol
        });
        let graphicsLayer = new GraphicsLayer();
        graphicsLayer.add(graphic);
        this.view.map.add(graphicsLayer);
		this.view.goTo(graphic);
    }
}

From that point, you're really onto just 3.X vs 4.X API changes in your code. Anyways, hope this makes sense and helps some others.