|
BLOG
|
Maybe you've heard of them, but weren't quite sure what they are. You've undoubtedly been using them in your ArcGIS API for JavaScript development this whole time and didn't even know it. Maybe they are the bane of your existence and increasing gray hairs. Love them or hate them, but learn how to use them. Promises, they flow like cracks in the wall. You can read more about EcmaScript 2015 Promises here. Here is the spec, if you are so inclined to read it. When I say you've probably been working with them all this time and didn't even know, let's look at the Retrieve data from a web server guide in the ArcGIS JS API docs. See all the references to the stuff like doSomthing().then(function(){}). That's a Promise. The Promises used in the JS API are based on the Dojo Promise, which has been around longer than most Promise implementations. That is why it doesn't have all the methods defined above. But it works just as well. This Promise module is just the API for a Promise. In the ArcGIS API for JavaScript, we are typically dealing with Promises via dojo/Deferred. Some might say that Promises are the monads of asynchronous programming. Hiding in plain sight If you look throughout the ArcGIS JS API documentation, you will see that Deferred is the return type for plenty of methods. Just look at the methods on the map. But why? A Promise is used to work with some sort of asynchronous activity. It could be using the QueryTask to make some requests that could take a few milliseconds or a couple of seconds, it's a roll of the network dice sometimes. The point being is that if we didn't use a Promise implementation to handle these asynchronous requests, your application would spend most of it's time just sitting there, frozen in fear, waiting for responses. A Promise says, "look, I promise I'll be back, one way or another I'm coming back, but keep fighting the good fight. Do you what you have to do and just wait for my triumphant return!" It's a brave little worker. While a Promise is working, once it has accomplished it's task, it will resolve with it's result or if something goes wrong, it will reject it and hopefully give you a reason. Thus, when you are the one wielding the power of a Promise and you find yourself using deffered.resolve(), you should also take care to figure out when you should use deferred.reject() and handle these errors. You want to play a prank on your users? Zoom to the map on click, but then zoom back to where they were before they clicked. It will drive them nuts. require(["esri/map", "dojo/domReady!"], function(Map) {
var map = new Map("map", {
center: [-118, 34.5],
zoom: 8,
basemap: "topo"
});
map.on('click', function(e) {
var pt = map.extent.getCenter();
map.centerAndZoom(e.mapPoint, 12);
map.centerAndZoom(pt, 8);
});
}); Look at the sample on JSBIN. Wait a second! That doesn't seem to work. It's not very consistent and not very funny. What's happening is that the centerAndZoom method is comprised of a smooth zoom, a gradual, almost animation like effect from one zoom level to the next. That's not an instantaneous action. Somewhere in there, you need to wait for the zoom to finish and then you can go back to where you were. That's why centerAndZoom returns a Deferred. This means we can rewrite the above like this. require(["esri/map", "dojo/domReady!"], function(Map) {
var map = new Map("map", {
center: [-118, 34.5],
zoom: 8,
basemap: "topo"
});
map.on('click', function(e) {
var pt = map.extent.getCenter();
map.centerAndZoom(e.mapPoint, 12).then(function() {
map.centerAndZoom(pt, 8);
});
});
}); JSBIN here. That is much better. And absolutely hilarious. Chain it up One thing you can o with Promises is chain the results. Let's look at this sample from the docs. It performs a query and displays information on the page. The bulk of the work is done here. function execute () {
query.text = dom.byId("stateName").value;
queryTask.execute(query, showResults);
}
function showResults (results) {
var resultItems = [];
var resultCount = results.features.length;
for (var i = 0; i < resultCount; i++) {
var featureAttributes = results.features.attributes;
for (var attr in featureAttributes) {
resultItems.push("<b>" + attr + ":</b> " + featureAttributes[attr] + "<br>");
}
resultItems.push("<br>");
}
dom.byId("info").innerHTML = resultItems.join("");
} There's nothing wrong with that, but it could get a little tricky if you wanted to say, omit some attributes from being displayed or change the DOM elements being created. It just seems like an awful lot of work in little spot. Well, you can chain the Promise returned from a QueryTask like below. function execute () {
query.text = dom.byId("stateName").value;
queryTask.execute(query).then(function(results) {
// get the attributes
return results.features.map(function(x) {
return x.attributes;
}).shift(); // since we know there is only one result, return first attribute
}).then(function(attributes) {
// Create the DOM strings
return Object.keys(attributes).map(function(key) {
return "<b>" + key + ":</b> " + attributes[key] + "<br>";
});
}).then(function(x) {
// Join the DOM strings
return x.join("");
}).then(function(elements) {
// update the DOM
dom.byId("info").innerHTML = elements;
}).otherwise(function() {
alert("Something went completely wrong");
});
} JSBIN here. As you can see, as long as you keep returning a result in the functions used in the then method, you can chain them. For demonstration purposes, I chained it a little more than I normally would, but you can now easily distinguish what parts of the chain are doing what work. So you can add a new piece to the chain to do extra work or modify an existing chunk to fix it. Notice the otherwise method. This will capture any errors that occur. For example, try searching for CaliforniaFun and see what happens. Fun fact. You can return a Promise in a Promise chain, so maybe inside a then function, you need to do some async requests, maybe merge with another data source, just return a Promise and continue the chain. Enjoy your magic functions. This is actually a more useful way of chaining Promises, to actually chain async requests. Check out this post from Sitepen for more details. Done for now, I Promise As you can see, Promises and their implementation in Deferred is pretty powerful. They are great tools for use with asynchronous tasks. One thing to remember is that Promises will execute right away. You just get to defer when the results get handled. If you want to try some other techniques that will defer execution until you are ready, you can try out RxJS or look at something like Folktales data.task which implements a Future monad. But I'll leave those goodies for you to explore. For more geodev tips and tricks, check out my blog.
... View more
06-17-2015
09:00 AM
|
3
|
0
|
5113
|
|
BLOG
|
So all the cool kids on the block have data binding.You can do it in Angular, React, or Ember. In each of these cases you just kind of get data binding to the DOM out of the box. Dojo doesn't quite work that way. You can do it for sure with a little work by using Stateful watch and updating the DOM when data changes. If you are using dijits, you can definitely bind to Stores and you get updates, but how about with regular old DOM elements. That's where something like dbind comes into play. dbind falls into the category I like to refer to as Dojo friendly packages. Meaning it was written to work with Dojo, mainly as side projects of a Dojo contributor. Some of these have become full blown projects under SitePen. A recent example would be dstore. Getting tied up So what does dbind bring to the table? I've talked about some uses before in this post and even some advanced uses in this post. What dbind, in it's simplest form lets you do is watch for changes on an object and bind those changes to something else. For example, maybe you want to bind some text element in your application to activity on your map. You could do something like this to bind to the mouse-move event of the map. require([
"dojo/_base/declare",
"dojo/dom",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dbind/bind",
"esri/map",
"esri/layers/FeatureLayer",
"dojo/domReady!"
], function(
declare, dom, _WidgetBase, _TemplatedMixin,
bind,
Map, FeatureLayer
) {
var map = new Map("map-div", {
center: [-118, 34.5],
zoom: 5,
basemap: "topo"
});
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/5';
var layer = new FeatureLayer(url, { outFields: ['*'] });
map.addLayer(layer);
var template = '<div class="label-container"><div>X: <span data-dojo-attach-point="xNode">${x}</div><div>Y: <span data-dojo-attach-point="yNode">${y}</div></div>';
var LabelContainer = declare([_WidgetBase, _TemplatedMixin], {
templateString: template,
constructor: function() {
this.set('x', 0);
this.set('y', 0);
},
postCreate: function() {
bind(this.xNode).to(this, 'x');
bind(this.yNode).to(this, 'y');
}
});
var lblContainer = new LabelContainer(null, dom.byId('lbl-div'));
map.on('mouse-move', function(e) {
lblContainer.set('x', e.mapPoint.x);
lblContainer.set('y', e.mapPoint.y);
});
}); You could see a sample of this in action here. As you can see, you can bind DOM elements to changes on an object, and in this case, as you move your mouse around the map, the text in the element changes. Now that's pretty cool. The coordinates however seem to be a little too accurate, your users don't need that level of detail, so you can use dbind to bind those changes to a function and then bind the DOM to the results of that function. Sound confusing? It's just one change to this code you can do like this. var fixed3 = function fixed3(n) {
return n.toFixed(3);
};
var LabelContainer = declare([_WidgetBase, _TemplatedMixin], {
templateString: template,
constructor: function() {
this.set('x', 0);
this.set('y', 0);
},
postCreate: function() {
var roundX = bind(fixed3).to(this, 'x');
var roundY = bind(fixed3).to(this, 'y');
bind(this.xNode).to(roundX);
bind(this.yNode).to(roundY);
}
}); You can see this sample here. More tools in your toolkit As you can see, dbind can come in pretty handy in your application development. Maybe you have found yourself relying on watching for changes on a Stateful object and lots of boilerplate to accomplish what you think should be a simple task. dbind can help with that. Consider dbind just another tool in your toolkit. So read up on it a bit and see if it will prove useful for you. I've managed to do some pretty cool stuff using dbind with dojo/topic, so I'm sure you can definitely find some uses for it. For more geodev tips and tricks, check out my blog.
... View more
06-10-2015
08:43 AM
|
0
|
0
|
1079
|
|
BLOG
|
Do you develop web apps that require editing? Maybe you have a feature that lets users add graphics to the map? Do you use the TemplatePicker to do your editing? Why don't you try and spice things up a bit. We live in a world where we swipe and drag for everything on our devices. You may even swipe for your next date. But the idea is that users are becoming more sophisticated in their interactions with applications. They want something more intuitive. A while ago, I did a writeup on this very subject, but I haven't really checked to see if the code would work in the current version of the ArcGIS JavaScript API. I was all deep into CoffeeScript at this time, so the code may not be the easiest to read. So how could you accomplish something like this? Let's check out one solution. require([
"dojo/_base/declare",
"esri/map",
"dojo/Evented",
"dojo/dom",
"dojo/dom-geometry",
"dojo/dom-attr",
"dojo/on",
"dojo/query",
"esri/geometry/ScreenPoint",
"esri/geometry/screenUtils",
"esri/symbols/PictureMarkerSymbol",
"esri/graphic"
], function(declare, Map, Evented, dom, domGeom, domAttr, on, query, ScreenPoint, screenUtils, PictureMarkerSymbol, Graphic) {
var cleanup = function(targets) {
targets.map(function(x) {
x.remove();
});
};
var DragDropHandler = declare([Evented], {
dragdrop: function(srcName, targetName) {
var src = dom.byId(srcName);
var target = dom.byId(targetName);
var handlers = [];
var self = this;
handlers.push(on(target, 'dragenter', function(e) {e.preventDefault();}));
handlers.push(on(target, 'dragover', function(e) {e.preventDefault();}));
handlers.push(on(target, 'dragend', function(e) {cleanup(handlers);}));
handlers.push(on(src, 'dragstart', function() {
handlers.push(on(target, 'drop', function(e) {
e.preventDefault();
cleanup(handlers);
var position = domGeom.position(e.currentTarget);
var x = e.clientX - position.x; // in case the map does not take up whole page
var y = e.clientY - position.y;// in case the map does not take up whole page
self.emit('itemdrop', {
bubbles: true,
cancelable: true,
dragsource: src,
x:x,
y:y
});
}));
}));
}
});
var handler = new DragDropHandler();
var map = new Map("mapView", {
center: [-118, 34.5],
zoom: 8,
basemap: "topo"
});
function addPoint(data) {
var mp = screenUtils.toMapGeometry(map.extent, map.width, map.height, data.pt);
var pms = new PictureMarkerSymbol(data.url, 24, 24);
var graphic = new Graphic(mp, pms);
map.graphics.add(graphic);
}
on(query('.drag-icon'), 'mousedown', function(e) {
var srcName = domAttr.get(e.currentTarget, 'id');
handler.dragdrop(srcName, 'mapView');
});
on(handler, 'itemdrop', function(e) {
var data = {
pt: new ScreenPoint(e.x, e.y),
url: domAttr.get(e.dragsource, 'src')
};
addPoint(data);
});
}); That's not really a whole lot of code. The workhorse is really the DragDropHandler. This DragDropHandler has a dragdrop method that will listen for drag events on an image, and sees when the image is dropped on the map. When this image is dropped on the map, you just need to emit an event with screen coordinates and what the dragged item was. The ArcGIS JS API provides some utilities for you to convert screen coordinates into map geometries and voila, you can quickly add graphics to the map and even copy the image src to display it on the map! Here is an example on jsbin. This sample just adds graphics to the map, but there is no reason it couldn't be applied to editing, so you could drag and drop features to add them to a FeatureService. So give it a shot and add some pazazz to your apps! For more geodev tips and tricks, check out my blog.
... View more
06-03-2015
10:03 AM
|
1
|
0
|
852
|
|
POST
|
This looks confusing to me: this.geometryService.on('areas-and-lengths-complete', lang.hitch(this, 'showVerifyiedFieldGeometryResults', deferred)); I don't see in the source or docs that hitch takes a 3rd parameter. It looks like you are trying to pass it as a token. You could rewrite it like this: this.geometryService.on('areas-and-lengths-complete', this.showVerifyiedFieldGeometryResults(deferred));
//updated
showVerifyiedFieldGeometryResults: function(deferred){
return function(results) {
var acres = Math.floor(results.result.areas[0]);
//NOTE: Check acres to ensure the feature is not too big... code omitted...
console.log('geometry results'); //this line always executes so it appears something is wrong with the Deferreds...
deferred.resolve('Verified Field Size: ' + acres);
}
} I would try that
... View more
05-27-2015
03:18 PM
|
0
|
1
|
2800
|
|
BLOG
|
Have you ever started working on an application with some custom widgets and you begin to notice that maybe your widget is starting to grow a little unruly? Maybe it's gone from 50 lines of code to 100, maybe 200, 1000? Don't be ashamed, depending on your needs, some widgets can require a lot of stuff to get the job done, and that's what we're all about right? Getting the job done. Take a step back and start looking at how maybe you can break that widget up. Are you doing stuff like adding graphics to the map based on certain widget events? Are you listening for map activity to do some analysis using the current map extent and another service that may not be loaded in the map? Maybe you just need to send the users current location to a FeatureService every time a widget is activated, but you'd like to port this functionality across all widgets? I don't know your use case, but if you can step back and start breaking your widget down into smaller pieces, maybe we can start making things easier to manage. Break up the behavior A good way of looking at widget composition is looking at the behavior you need in your widget. For example, let's look at the ever famous dijit/_WidgetBase that you probably have used to build your own widgets. When you extend a _WidgetBase, you are given a set of behaviors most notably the dijit lifecycle, which is a set of methods you can override to build your widget during it's creation. Fore more details about custom widgets and the dijit lifecycle, you can check out this video on my blog. You also get another set of behavior, which is the set/get methods for attributes from dojo/Stateful, that allows you to watch for changes. Now if you want this widget to use a templated HTML string to define it's user interface, you can extend the dijit/_TemplatedMixin that adds one simple property for you, the templateString. That one some property allows you to build an entire UI around your widget. That's pretty powerful when you think about it. Be on your best behavior So how exactly could you start incorporating mixins into your own projects? Let's assume you notice that in your widgets you are constantly adding the ability to recenter the map when something happens. One way you can accomplish this is to break that behavior out into a mixin. I don't know if it's convention, but I notice in the dijit library that mixins are prefixed with an underscore, so let's make a mixin called _RecenterMixin. That could look something like this: define([
'dojo/_base/declare'
], function(declare) {
return declare(null, {
// this mixin works under the assumption
// that you have a map assigned to your widget.
// it also assumes the map was initialized with
// a center and zoom
recenter: function() {
var map = this.get('map');
var params = map._mapParams;
if (params.center) {
map.centerAt(params.center);
}
}
});
}); So now you can make a widget that extends this mixin and just call the recenter method when you need it. I can use it in a widget that wraps BasemapGallery and recenter the map when the base map is changed. define([
'dojo/_base/declare',
'dojo/_base/lang',
'dojo/dom',
'dojo/dom-construct',
'esri/domUtils',
'esri/dijit/BasemapGallery',
'dijit/_WidgetBase',
'dijit/_TemplatedMixin',
'./_RecenterMixin',
'dojo/text!./templates/BasemapSwitcher.html'
], function(
declare, lang, dom, domConstruct,
esriDomUtils, BasemapGallery,
_WidgetBase, _TemplatedMixin,
_RecenterMixin,
template
) {
return declare([_WidgetBase, _TemplatedMixin, _RecenterMixin], {
templateString: template,
postCreate: function() {
var node = dom.byId('map_root');
esriDomUtils.hide(this.domNode);
domConstruct.place(this.domNode, node);
var map = this.get('map');
this.gallery = new BasemapGallery({
map: map,
showArcGISBasemaps: true
}, this.bmNode);
this.gallery.startup();
this.gallery.on('selection-change', lang.hitch(this, function() {
this.recenter(); // MIXIN MAGIC
}));
},
hide: function() {
esriDomUtils.hide(this.domNode);
},
show: function() {
esriDomUtils.show(this.domNode);
}
});
}); Looking at this sample, I could even move the hide/show methods to a mixin and reuse it elsewhere Wait a second You might be asking yourself what is the difference between something like _WidgetBase and a mixin? Typically, a mixin on it's own is pretty useless. In the example above, if you just extended _RecenterMixin alone, you wouldn't have the postCreate method or a templateString to work with. A mixin may even depend on something like _WidgetBase, hooking into lifecycle methods. At the core of it, you are still extending modules, extending the behavior or widgets. I put a demo repo of this project up on github for you to play with. So go out there and see if you can start breaking out mixins in your project and maybe simplify the maintenance of your application. You might find you really like the ability to reuse behavior among different widgets, get more use out of all the hard work you put in to your code. For more geodev tips and tricks, check out my blog.
... View more
05-27-2015
09:44 AM
|
0
|
0
|
761
|
|
POST
|
The warning is actually coming from Chrome and pointing out where in the code it suggests a change be made. You can see the warning in the console of Chrome DevTools on the samples pages. ArcGIS API for JavaScript Sandbox I think the warning is unrelated to the actual script error you are getting.
... View more
05-21-2015
03:25 PM
|
0
|
4
|
3400
|
|
BLOG
|
Recently I was asked about how one might go about working with finding layers in the map. This is one of those things that can be approached in different ways. Manage layers (Tiled/Dynamic/Feature) added to map. Find layers in a service by Name, not just ID. In particular is number 2 above. I've been there. You put an awesome app together, you've got queries built, maybe some custom search functionality, cool spatial analyses based on the returned results. things are working awesome and then someone adds a new layer to a map service and all your hopes and dreams come shattering around you. Yeah, it happens. The needs of a map service change and services can be used for multiple applications. If you are the one handling the server updates as well as development, it's annoying, sure, but not so bad to maintain. If you are the poor soul who is at the will of the ArcGIS Server admin who seems to relish in rearranging the order of the layers just to feast on your tears, I feel for you. Managing Map Services I can't offer you some grand solution or quick fix, but I can offer up some tips on how to get to know your data and ways you can search it. Let's start simple, just managing services in a map. The documentation has a real easy sample on how to get all the layers from map. This is a great first step, because now you are not dependent on layerIds to find your data. Fantastic! But you should also get in the habit of providing an id property to your layers when you create them. Something like this: var dLayer = new ArcGISDynamicMapServiceLayer(url, {
id: "HydroStuff" // useful for searching later on
}); This lets you do this: var layer = map.getLayer("HydroStuff"); Ok, not groundbreaking stuff I know, but when working with your apps, this makes it very simple to make sure you are doing queries or selections on the correct layers. You can also provide the id in the JSON of the WebMap Spec of the layer. It should be noted, the docs say the id is for the position in the map, but so far (crosses fingers) I haven't had issue tweaking this on my own. That's all pretty cool, but you can also get a little fancy with your searching if the names are similar or you don't trust your ArcGIS Server admin not to mess with the names too... var getLayersFuzzySearch = function(m, term) {
var queries = arrayUtils.filter(m.layerIds, function(x) {
return x.toLowerCase().indexOf(term.toLowerCase()) > -1;
});
return arrayUtils.map(queries, function(x) {
return m.getLayer(x);
});
};
var fuzzy = getLayersFuzzySearch(map, "hydro"); This may not be ideal in all situations, but it does come in handy. Getting to the layer of the matter But how, you ask, do you deal with actual layers in a service that may change position? Well, I'll throw this little sample out there to give you an idea. var layerMap = {}; // A layer map to hold a reference to all layers by name
// List out the layers in a service
// Useful when working with FeatureServices
esriRequest({
url: url,
content: { f: "json" },
handleAs: "json",
callbackParamName: "callback"
}).then(function(x) {
return x.layers;
}).then(function(x) {
return arrayUtils.map(x, function(item) {
layerMap[item.name] = item.id; //dictionary that sucker!
return domConstruct.create("li", {
"data-layer-id": item.id,
innerHTML: "ID: " + item.id + " | Name: " + item.name
});
});
}).then(function(x) {
var node = document.getElementById("layer-items");
arrayUtils.map(x, function(item) {
node.appendChild(item);
});
}); This little sample writes the layers out to the page for you, but what is cool is the layerMap object being populated. This is now set up with the names to point to layer ids. So if you want to get the id of a layer, just reference it by name, such as layerMap["Rivers"], which will return 1 in this scenario. If your ArcGIS Server admin is changing positions of your layers and the names, you probably got them a lousy Secret Santa gift and should make amends. I put a quick JSBin to help demonstrate some of the stuff above. There is no super solution to reference layers by name in a service most of the time, but the data is there for you to write up a solution of your own. The above just happens to be how I like to handle it when I'm unsure of finalness (that can't be a real word) of the map services to mitigate any pains down the road. If you have some other sleeker solutions, I'd love to see them. Your pains may have been greater than my own. For more geodev tips tricks, check out my blog.
... View more
05-20-2015
09:39 AM
|
0
|
0
|
893
|
|
POST
|
For Web AppBuilder, it looks like widgets have an onClose method you could use to do what you are trying to accomplish. Communication to app container—Web AppBuilder for ArcGIS (Developer Edition) | ArcGIS for Developers Granted, I haven't tried this myself, but it might help.
... View more
05-14-2015
01:30 PM
|
4
|
1
|
2243
|
|
POST
|
If that's the case, then you can ignore using RequireJS and just use the API as is via the samples.
... View more
05-13-2015
10:30 AM
|
0
|
4
|
7628
|
|
POST
|
Is this a larger requirejs application that you are trying to add the ArcGIS API to or is this a fresh new application? Dojo has it's own AMD loader that is very similar to RequireJS (as an AMD loader), both written by the same guy actually, but there are some differences. If you are using the ArcGIS JS API, you already have the Dojo loader available to you. More info here. Writing a Class | Guide | ArcGIS API for JavaScript One of the differences between RequireJS and Dojo, is the config setup. RequireJS has require.config(), but in Dojo, you can just pass require(configObject). I just posted on other ways to set up the config in a blog post. The roads to starting an ArcGIS JS API app For most cases you won't need to use RequireJS with Dojo. If you need some clarification, just let me know. Thanks! Edit - I should also point out that all the plugins RequireJS can use like text, domReady are available with Dojo. order is no longer used in RequireJS. Dojo does not have a shim property for the config unfortunately, but you can still do it similar via some define(['lib'], function() {return globalLibName}); There might be a better way to do that now, but that's how I did it. jQuery is already AMD compatible, you may need to set up like in this thread. define.amd.jQuery = true;
... View more
05-13-2015
09:00 AM
|
1
|
6
|
7628
|
|
BLOG
|
If you are using the ArcGIS API for JavaScript to build a moderately sized application, you are probably building it from different modules. If that's the case, you are probably using a standard dojoConfig as described in the online documentation. What you may not be aware of is that the global dojoConfig isn't the only way to start the party. No one true path So here is how you might traditionally set up your dojoConfig start your app. Option 1 var dojoConfig = { /*stuff*/ };
require(['dependency1', 'dependency2'], function(dep1, dep2) {
/* awesome web dev stuff */
}); You could also add it to the script tag. Option 2 <script src="/* esrijs api url */" data-dojo-config="isDebug:true, packages=[/* stuff */]"></script> But I find this won't really work when a larger config may be needed. This is another viable approach. Option 3 require({
packages: /* stuff */
});
require(['dependency1', 'dependency2'], function(dep1, dep2) {
/* awesome web dev stuff */
}); But there is yet another way to configure and start your application. Option 4 require({
packages: [{
name: 'app', location: /*somewhere*/
}. {
/* more packages */
}]
}, ['app']);
// app/main.js in your app
require(['dependency1', 'dependency2'], function(dep1, dep2) {
/* awesome web dev stuff */
}); As we saw in this post on Dojo modules, when you ask for a whole package, such as a directory with Dojo, it assumes there is a main.js file in the directory for that package. That's how this works. Some caveats I'm not sure if I've written about using ES6 for ArcGIS JS Dev, but I did do a presentation on it at the most recent Developer Summit. You can see the video here. One of the caveats when using ES6 is that transpilers, like Babel will convert import statements to define statements, but there is no way to get a require statement. So it is still up to you to create at least one single require entry point for your application to get started. So this: import map from 'esri/map'; Turns into this: define(['esri/map'], function(Map) { /*stuff*/ }); Ok. it's not exactly that, transpilers when compiling to AMD modules will use the exports and modules modules to do some stuff that may look odd in the compiled code, but the end result is basically as shown above. For this reason, I typically do this now. <script>dojoConfig = { /* config stuff */ };</script>
<script src="//js.arcgis.com/3.13/"></script>
<script>require(['app/main'], function(){});</script>
// app/main.js
define(['dependency1', 'dependency2'], function(dep1, dep2) {
/* awesome web dev stuff */
}); You can see a sample of this in a testing repo where I was using TypeScript. Enjoy your choices So there is more than one way to start a Dojo app. This just goes to show there is some flexibility in the toolkit to fit your developer needs. I'd suggest experimenting with them and finding what best fits your needs. You may even find that a different approach is appropriate in different situations, so it's at least a good idea to know them. This is one of those nuanced bits of knowledge that just gives you better insight into how Dojo works. For more geodev tips and tricks, check out my blog.
... View more
05-13-2015
08:48 AM
|
2
|
0
|
2459
|
|
POST
|
Hey Steve, I was writing a reply, but it got kind of big and this was a topic on my TODO for GeoNet blog posts anyway, so I put a post up. Let me know if that helps at all or any bits that are still unclear. How you organize the files is preference, so my you have a utils folder or a widgets folder, maybe organize it by function, what Ember refers to as Pods. This is the organizational structure I prefer, but again, in general, it's a preference. Embrace your AMD modules If that's still unclear, if you have a small sample legacy project, I'd be happy to go through it and try to document the migration. It would be a good exercise in documenting the process. Thanks.
... View more
04-29-2015
09:35 AM
|
0
|
1
|
3235
|
|
BLOG
|
A question on the forums recently popped up that I thought maybe I could explain better in blog post form rather than a single reply. How do I migrate from legacy Dojo to modern Dojo AMD style? That's a valid question, and one I struggled with when Dojo 1.7 was released. You can refer back to the Migrating to 3.0 in the docs to get an idea of the changes. I think at this point, people are familiar that dojo.declare is now a module called dojo/_base/declare, but it's how to use AMD bits that causes some confusion. I think some of the hurdles is just wrapping your head around AMD. I had some brain twisting moments with it at first, as I wasn't sure when to use require or when to use define or how to work with modules. My initial struggles were with requirejs and I had no mentor to really help me out, but I scraped through docs and google groups postings to figure it out, so when Dojo 1.7 was released with the AMD loader, I was better prepared. Let's break it down to it's simplest form and what you will see in most examples on the ArcGIS Developers site. require(["esri/map", "dojo/domReady!"], function(Map) {
var map = new Map("map", {
center: [-118, 34.5],
zoom: 8,
basemap: "topo"
});
}); There is the dreaded require method. If you have ever worked in C++/Java or similar languages you may be familiar with the main() function. This is the function that occurs when an application starts up, it's where the party gets started. The require method is the main function of AMD. So somewhere in the ArcGIS API for JavaScript modules downloaded via CDN when you add the script tag to your page is a JavaScript file called map.js in a folder called esri that looks like this. define(['dependency1', 'dependency2'], function(dependency1, dependency2) {
var Map;
/*magic unicorn stuff*/
return Map;
}); The require method will go look for that file and load it asynchronously to your application. When it is done loading, the function in your application is then called, the stuff that defines what a Map is (very existential) is returned from the function. Notice the that this module has it's own dependencies, and in the function they are loaded in the order that they are asked for. THIS IS IMPORTANT. Order matters. These dependencies could functions or objects, or maybe strings. Below is a diagram from my book that may help you out. In my book, I use a file called run.js that does the require method defining my dojoConfig and a main.js that actually starts doing the work. That just happens to be my style, but it's not the definitive style. Some people get tripped up by the use of main.js and honestly, I did too at first. A good point of reference is the Advanced Modules tutorial from Dojo. I'll quote the important bit under Configuring the Loader. main (optional, default = main.js😞 used to discover the correct module to load if someone tries to require the package itself. For example, if you were to try to require "dojo", the actual file that would be loaded is "/js/dojo/main.js". Since we’ve overridden this property for the "my" package, if someone required "my", they would actually load "/js/my/app.js". If we tried to require "util", which is not a defined package, the loader would try to load "/js/util.js". You should always define all of your packages in the loader configuration. Here is a sample of what one of my dojoConfigs, my require entry point, may look like. var pathRX = new RegExp(/\/[^\/]+$/)
, locationPath = location.pathname.replace(pathRX, '');
require({
packages: [{
name: 'widgets',
location: locationPath + 'js/widgets'
}, {
name: 'utils',
location: locationPath + 'js/utils'
}, {
name: 'app',
location: locationPath + 'js'
}]
}, ['app']); In the require method, the first parameter is the dojoConfig object, the second is the ['app'] dependency, this is my entry point. So according to the docs above, by simply asking for 'app', by default it will look for 'app/main.js'. If all this is still too confusing, you can still just define a global dojoConfig object to configure your packages and do a plain older require to start your application. Something like this. <body class="nihilo">
<script>
var pathRX = new RegExp(/\/[^\/]+$/)
, locationPath = location.pathname.replace(pathRX, '');
var dojoConfig = {
async: true,
isDebug: true,
packages: [
{ name: 'xstyle', location: locationPath + '/bower_components/xstyle' },
{ name: 'mayhem', location: locationPath + '/bower_components/mayhem/dist' },
{ name: 'app', location: locationPath + 'js' }
],
tlmSiblingOfDojo: false
};
</script>
<script>require(['app/start'], function() {})</script>
</body> Hopefully that clears some stuff up a bit. You could learn a lot of this via the Dojo docs and especially the CDN Modules tutorial. This page covers some Legacy to Modern Dojo bits in depth. Here is a migration guide. Here is a legacy Dojo to AMD converter you can also try. I've never used it, but it could be a good starting point. I doubt it will work 100% with all Esri modules, but it's worth a shot. I hope that helps out a bit if you are still struggling with migrating Legacy Dojo applications to modern Dojo. It may take some time to let it all sink in, but it will. For more geodev tips and tricks, check out my blog.
... View more
04-29-2015
09:33 AM
|
7
|
4
|
8671
|
|
BLOG
|
So about two years ago or so Esri joined the world of developers who are using github on a regular basis to host and share code. As a developer, I'm sure there were many at Esri who were just ecstatic about being able to share their code and maybe more importantly get the community of current and new GIS developers involved. If you haven't yet gone through the Esri Github pages, you really should check it out. So of course, there is an Esri Github page you can search by programming language to find a wealth of samples and projects. I'm mainly a JavaScript guy, so some of my recommended projects to check out are as follows in no particular order: Esri/jsapi-resources · GitHub Esri/Terraformer · GitHub Esri/esri-leaflet · GitHub Esri/bootstrap-map-js · GitHub Esri/angular-esri-map · GitHub Esri/dojo-bootstrap-map-js · GitHub There's also quite a few in the weeds projects that do some heavy lifting or are just plain interesting. Esri/koop · GitHub Esri/pushlet · GitHub Esri/wind-js · GitHub Then you can start delving into other languages and some pretty neat spatial tools. Esri/geometry-api-java · GitHub Esri/rabbitmq-for-geoevent · GitHub Esri/spatial-framework-for-hadoop · GitHub Esri/R-toolbox-py · GitHub That's just a small sampling of the projects available via the Esri repos. Some of the widgets using the ArcGIS API for JavaScript are available in the repo and since they are on github, you could even contribute to them. That's the beauty of having code on github. There are plenty of samples and templates in their repo that you can use. Going through the repos, I see plenty of Esri people submitting issues and pull-requests, but I also see a lot of regular users doing the same thing and contributing to projects. It can be as simple as submitting an update to the documentation or updating an project to use the latest version of an API. It is after all, all about community. For more geodev tips and tricks, check out my blog.
... View more
04-24-2015
07:30 AM
|
2
|
0
|
3544
|
|
POST
|
We just ran into a similar issue this week after updates were applied to our Oracle Database. Turns out it was a known bug and there is a patch/service pack update we applied to all machines with ArcMap and ArcGIS Server to fix it. 43293 - ArcGIS cannot connect to an Oracle database after installing Oracle Critical Patch Update for October 2014 That may or may not be the issue you are having, but it fits exactly to what I experienced this week.
... View more
04-22-2015
06:37 AM
|
3
|
0
|
2286
|
| Title | Kudos | Posted |
|---|---|---|
| 2 | a week ago | |
| 1 | a month ago | |
| 2 | 3 weeks ago | |
| 2 | 05-19-2026 02:12 PM | |
| 1 | 04-24-2026 11:01 AM |
| Online Status |
Offline
|
| Date Last Visited |
yesterday
|