Esri's samples that demonstrate the display of tabular data alongside a map involve the usage of a Dojo class called dgrid. (Samples written for earlier versions of the API used an older Dojo class called datagrid.) See Dojo's website for documentation. Open this example and view the source code to follow along with the discussion below. The example builds on the map of Pennsylvania counties from the previous page. Let's focus on what was added to that example.
Begin by examining the HTML at the bottom of the page. In addition to the usage of the dgrid class, this code also demonstrates how to use Dojo's BorderContainer and ContentPane classes to aid in laying out the page. Note that a div (with id of container) is used as a container for two other divs; one for the map (id of mapDiv) and one for the sidebar (id of sidePane). The sidePane div in turn contains a div with an id of grid. I won't go into great detail on the usage of the BorderContainer and ContentPane classes other than to ask you to note how:
- objects of those classes are instantiated and associated with the divs using the data-dojo-type attribute,
- the child divs within the BorderContainer div have a region attribute setting, which defines their positions (other valid values for the region attribute include left, top and bottom).
In this example, the ContentPane used to display the sidebar table is positioned to the right and the ContentPane used to display the map is positioned in the center (the setting used to force an element to take up all the space not occupied by the other elements in the container).
Lines 39-64 of the script define references to the modules containing the needed classes, while lines 65-85 define variables that will provide access to those classes. Don't be troubled by the fact that each of these references/variables appears on its own line unlike the examples from earlier in the lesson. Because the order of the variables needs to match the order of the modules, this formatting style may make a lot of sense in situations like this where you have a lot of modules/variables to keep in order.
The first line in the callback function -- parser.parse() -- scans through your HTML looking for specially decorated Dojo elements and converts them to dijits. In this case, it will convert the BorderContainer and ContentPane divs that we discussed moments ago.
Lines 91-97 are concerned with setting up the grid that will appear in the sidebar. First, a new custom class is defined using the Dojo declare() method that combines the Grid and Selection classes. This class is given the name StandardGrid. A new object of this custom class is then created with some properties set as the first argument to the constructor: bufferRows is set to Infinity to render all of the service's rows as opposed to using a value like 10, which would render 10 rows above and 10 rows below the ones currently visible; selectionMode is set to "single" to allow only one row to be selected at a time; sortable is set to true, allowing users to click on a table header to sort by that column. The second argument to the constructor -- which is critical -- is the ID of the DOM element you want to attach the grid to. In this case, our code has a div embedded within the sidePane div with an id of grid. The last statement in this block of code is a call to the startup() method, which completes the programmatic creation of the grid dijit.
The next block of code (Lines 99-117) involve creating a listener for selections made on the grid (i.e., if a user clicks on one of the rows). It will be easier to follow what's happening here after discussing some of the code below, so we'll come back to it later.
Lines 119-125 should look a bit more familiar to you as they are concerned with creating the Map itself.
In Lines 128-131, a listener for the Map's onLoad event is defined. This listener does two things:
- Using the Dojo dom-style module, it sets the visibility property of the container div to visible. Note the use of registry.byId("container").domnode, which is a Dojo equivalent to the getElementById() method we used earlier in the course.
- It calls on a function called populateGrid(), which inserts data into the grid. More on that later.
Next, we see lines (133-161) that are similar to what we've seen in earlier examples concerned with setting up the info window content, creating the FeatureLayer object that will be used to display the county features, filtering out unwanted counties with a definition expression, creating a fill symbol and applying it through a renderer, and adding the layer to the map. One thing to make particular note of in this code is the setting of the FeatureLayer's id property, which comes in handy later.
Lines 164-169 create listeners for when the user mouses over (turning the cursor into a pointer) and mouses out of (changing it back to a default cursor) the counties layer.
On Line 172, we encounter the populateGrid() function that was called upon earlier. The function starts by creating a QueryTask object and associating the counties map service with it. (As shown in the documentation, a QueryTask can be executed in a few ways: to retrieve just the count, extent or IDs of the features meeting the query criteria, or to retrieve the features themselves. In this case, we'll use the "normal" execute method to get back the features.) A Query object is created to go along with the QueryTask. The Query is set to retrieve just PA counties, geometry data is excluded and only certain fields are retrieved.
The FeatureSet returned by the query is available in the callback function through the results variable. The ultimate goal is to get the data into a form that the grid likes, which is what is happening in Lines 178-194. The Dojo array class and its map() method are use to iterate through each item in the results FeatureSet. The map() method takes an array as its input (here results.features, which gets the features in the FeatureSet) and through the callback function "maps" (or converts) each value to something else. In this case, we create an array having keys "ObjectID", "NAME" and "POP2007". We assign values to go along with those keys using the expression feature.attributes[outFields[X]], where X is the appropriate index for the desired field. Recall that outFields was defined as a global variable and held the names of the ObjectID and the 2000 and 2007 population and population density fields. The page is set up to show all of that data in the info window, but here in the sidebar we're only going to show the ObjectID, county name and 2007 population. ObjectID is at position 0 in the outFields array, NAME is at position 1, and POP2007 is at position 3. After this iteration through all of the counties is complete, the resulting array gets stored in the variable called data.
On Line 188, a new Dojo data store is created (specifically, a Memory store). Data stores are used in Dojo to bind data to dijits, in this case data from a map service to a dgrid dijit. The Memory class implements Dojo's object store API, which means it has a data property (used to set the data to be held in the store) and an idProperty property (used to specify a field in the data that uniquely identifies each object in the store). Here we set the data property equal to the data variable that was defined using array.map as described above. idProperty is set to ObjectID (though NAME would work in this instance as well). The object created on this line is stored in the variable memStore.
Unfortunately, there is a slight problem. The records returned by the Esri Query() method are not in a user-friendly order. (The Query class has an orderByFields property, which allows for ordering the results, but that property is not supported for all map services and it appears this is one of those not supported.) So, Lines 190-192 use Dojo's data store API method to query the store in memStore, sort it on the NAME field, and create a new Memory store (called newStore). Finally, the store gets associated with the grid created earlier on Line 193.
Users who click on a county on the map see an info window, but how do we get the info window to appear if the user clicks on a county in the sidebar? That behavior is added by the code we skipped over earlier on Lines 99-117. This code adds a listener for selections on the dgrid we created. (Selections can be made programmatically or by the user clicking on the grid.) The event variable in the callback function provides info on which rows were selected. Because we set the selectionMode to "single", we know that only one row can be selected. Thus, we know that we can ask for rows (i.e., the first and only row). By appending id onto rows, we're asking for the ID of that row. This value goes back to the idProperty setting made when we created the grid's data store. We set that to ObjectID, so our rowID variable will end up holding ObjectIDs. If we had set idProperty to NAME, rowID would instead hold NAME values.
Line 101 gets a reference to the counties FeatureLayer using the Map's getLayer() method and stores it in the fl variable. This method works because we assigned the layer an id as part of its creation on Line 142.
Next, we create a Query that retrieves features based on an array of object IDs (in this case, just the one ID associated with the clicked county) and use it along with the FeatureLayer's queryFeatures() method. Inside the callback function associated with that method, the Feature at position 0 in the FeatureSet can be safely retrieved since we know there was only one county object ID supplied to the query.
With the correct county identified, the var screenpoint statement gets the centroid of the selected county polygon and converts it to screen coordinates. The map's InfoWindow is then obtained and its title and content properties set equal to the title and content of the InfoWindow that was created for the selected county. The centroid coordinates are then passed to the InfoWindow's show() method, to ensure that the InfoWindowopens over the correct location.
One thing in this block of code that you may have found confusing was the if expression on Line 106 as you're probably used to seeing expressions that evaluate to True or False. However, it is not uncommon to see developers use an expression that returns a count, as in this line. If the returned value is 0, then the expression will evaluate to False. If it is any other value, it will evaluate to True.