How to display feature layer's attribute table with JavaScript API ver 4.9?
I am working with ArcGIS API for Javascript 4.9. I want to create a simple map view and display a table under it. I am stuck trying to work out how to display a table displaying data from a feature layer published on ArcGIS with this version of API. I removed all code related to the map to make it simpler till I work out this table.
Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tutorial: Grids and Stores</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
</head>
<body class="claro">
<h1>Display table with feature layer and dgrid </h1>
<div id="grid"></div>
<script src="https://js.arcgis.com/4.9/"></script>
<script>
require([
"dojo/_base/declare",
"dstore/RequestMemory",
"dgrid/Grid",
"dgrid/extensions/Pagination"
], function (declare, RequestMemory, Grid, Pagination) {
// Create a Grid instance using Pagination, referencing the store
var gridFields = ["__OBJECTID", "WAL_NAME", "REGION", "WAL_ZONE", "REGION_WAL", // __OBJECTID
"WAL_Value", "SWAL_Value"
];
var grid = new (declare([ Grid, Pagination ]))({
collection: new RequestMemory({ target: "https://xxxxxxxxx/arcgis/rest/services/GCX/QFESAlertLevels_GCX/FeatureServer/1?f=pjson" }),
className: 'dgrid-autoheight',
columns: gridFields,
}, 'grid');
grid.startup();
});
</script>
</body>
</html>
The error that I get is:
Any ideas? Did anyone get it to work?
Thanks!
Solved! Go to Solution.
Lidia,
OK here is the sample re-worked to show all the data.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>dGrid - 4.9</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
<script src="https://js.arcgis.com/4.9/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#info,
#gridDisplay {
position: absolute;
bottom: 0;
left: 0;
height: 35%;
background-color: white;
border-color: grey;
width: 100%;
font-family: "Avenir Next W00", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
#info {
z-index: 90;
font-size: 16px;
padding-left: 20px;
}
#info * {
padding-right: 20px;
}
#gridDisplay {
z-index: 80;
}
.info {
line-height: 20px;
padding-left: 5px ! important;
}
.dgrid-header,
.dgrid-header-row {
background-color: #eee;
color: #57585A;
}
.dgrid-row-even {
background-color: #F7F8F8;
}
.dgrid-row-odd {
background-color: #EFEFEF;
}
.dgrid-selected {
background: #B4DAF5;
}
.dgrid-row {
border: none
}
</style>
<script>
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/CSVLayer",
"esri/Graphic",
"esri/widgets/Legend",
"esri/widgets/Expand",
"esri/geometry/SpatialReference",
"dgrid/OnDemandGrid",
"dgrid/extensions/ColumnHider",
"dojo/store/Memory",
"dstore/legacy/StoreAdapter",
"dgrid/Selection"
],
function(
WebMap, MapView, CSVLayer, Graphic, Legend, Expand, SpatialReference,
OnDemandGrid, ColumnHider, Memory, StoreAdapter, Selection
) {
let map, view, csvLayer, csvLayerView, grid;
const gridDiv = document.getElementById("grid");
const infoDiv = document.getElementById("info");
// create new map, view and csvlayer
setupTheView();
const gridFields = ["__OBJECTID", "Category", "Season", "Name",
"Nature", "wmo_wind"
];
// create a new datastore for the on demandgrid
// will be used to display attributes of selected features
const dataStore = new StoreAdapter({
objectStore: new Memory({
idProperty: "__OBJECTID"
})
});
// create a grid with given columns once the csvlayer is loaded
csvLayer.when(function() {
// create a grid with columns specified in gridFields variable
createGrid(csvLayer.fields);
// get a reference the csvlayerview when it is ready. It will used to do
// client side queries when user draws polygon to select features
view.whenLayerView(csvLayer).then(function(layerView) {
csvLayerView = layerView;
popGrid();
});
})
.catch(errorCallback);
/****************************************************
* Selects features from the csv layer that intersect
* a polygon that user drew using sketch view model
****************************************************/
function popGrid() {
view.graphics.removeAll();
if (csvLayerView) {
const query = {
where: "1=1",
outFields: ["*"]
};
// query graphics from the csv layer view. Geometry set for the query
// can be polygon for point features and only intersecting geometries are returned
csvLayerView.queryFeatures(query).then(function(results) {
const graphics = results.features;
// if the grid div is displayed while query results does not
// return graphics then hide the grid div and show the instructions div
if (graphics.length > 0) {
gridDiv.style.zIndex = 90;
infoDiv.style.zIndex = 80;
document.getElementById("featureCount").innerHTML =
"<b>Showing attributes for " +
graphics.length.toString() + " features </b>"
} else {
gridDiv.style.zIndex = 80;
infoDiv.style.zIndex = 90;
}
// get the attributes to display in the grid
const data = graphics.map(function(feature, i) {
return Object.keys(feature.attributes)
.filter(function(key) {
// get fields that exist in the grid
return (gridFields.indexOf(key) !== -1);
})
// need to create key value pairs from the feature
// attributes so that info can be displayed in the grid
.reduce(function(obj, key) {
obj[key] = feature.attributes[key];
return obj;
}, {});
});
// set the datastore for the grid using the
// attributes we got for the query results
dataStore.objectStore.data = data;
grid.set("collection", dataStore);
})
.catch(errorCallback);
}
}
/************************************************
* fires when user clicks a row in the grid
* get the corresponding graphic and select it
*************************************************/
function selectFeatureFromGrid(event) {
// close view popup if it is open
view.popup.close();
// get the ObjectID value from the clicked row
const row = event.rows[0]
const id = row.data.__OBJECTID;
// setup a query by specifying objectIds
const query = {
objectIds: [parseInt(id)],
outFields: ["*"],
returnGeometry: true,
outSpatialReference: view.SpatialReference
};
// query the csvLayerView using the query set above
csvLayerView.queryFeatures(query).then(function(results) {
const graphics = results.features;
// remove all graphics to make sure no selected graphics
view.graphics.removeAll();
view.goTo(graphics[0].geometry);
// create a new selected graphic
const selectedGraphic = new Graphic({
geometry: graphics[0].geometry,
symbol: {
type: "simple-marker",
style: "circle",
color: "orange",
size: "12px", // pixels
outline: { // autocasts as new SimpleLineSymbol()
color: [255, 255, 0],
width: 2 // points
}
}
});
// add the selected graphic to the view
// this graphic corresponds to the row that was clicked
view.graphics.add(selectedGraphic);
})
.catch(errorCallback);
}
/************************************************
* Creates a new grid. Loops through poverty
* csvLayer's fields and creates grid columns
* Grid with selection and columnhider extensions
*************************************************/
function createGrid(fields) {
var columns = fields.filter(function(field, i) {
if (gridFields.indexOf(field.name) !== -1) {
return field;
}
}).map(function(field) {
if (field.name === "__OBJECTID") {
return {
field: field.name,
label: field.name,
sortable: true,
hidden: true
};
} else {
return {
field: field.name,
label: field.alias,
sortable: true
};
}
});
// create a new onDemandGrid with its selection and columnhider
// extensions. Set the columns of the grid to display attributes
// the hurricanes cvslayer
grid = new(OnDemandGrid.createSubclass([Selection, ColumnHider]))({
columns: columns
}, "grid");
// add a row-click listener on the grid. This will be used
// to highlight the corresponding feature on the view
grid.on("dgrid-select", selectFeatureFromGrid);
}
/******************************************************
* Sets up the view. WebMap with winkel III projection
* basemap and hurricanes CsvLayer is added to the view.
******************************************************/
function setupTheView() {
const url =
"https://arcgis.github.io/arcgis-samples-javascript/sample-data/hurricanes.csv";
csvLayer = new CSVLayer({
title: "Hurricanes",
url: url,
spatialReference: new SpatialReference({wkid: 54042}),
copyright: "NOAA",
popupTemplate: {
title: "{Name}",
content: [{
type: "text",
text: "Category {Category} storm with that occurred at {ISO_time}."
}, {
type: "fields",
fieldInfos: [{
fieldName: "wmo_pres",
label: "Pressure"
}, {
fieldName: "wmo_wind",
label: "Wind Speed (mph)"
}]
}],
fieldInfos: [{
filedName: "ISO_time",
format: {
dateFormat: "short-date-short-time"
}
}]
},
renderer: {
type: "unique-value",
field: "Category",
uniqueValueInfos: createUniqueValueInfos()
}
});
map = new WebMap({
// contains a basemap with a Winkel III projection
// the CSVLayer coordinates will re-project client-side
portalItem: {
id: "7d127cef99a44327b79f5185602b8b6b"
},
layers: [csvLayer]
});
view = new MapView({
container: "viewDiv",
map: map,
highlightOptions: {
color: "#2B65EC",
fillOpacity: 0.4
},
padding: {
bottom: infoDiv.clientHeight
}
});
const legendExpand = new Expand({
view: view,
content: new Legend({
view: view,
style: "card"
})
});
view.ui.add(legendExpand, "top-left");
}
/*********************************************************
* Used to create uniques values for the csvlayer renderer
*********************************************************/
function createUniqueValueInfos() {
const fireflyImages = [
"cat1.png",
"cat2.png",
"cat3.png",
"cat4.png",
"cat5.png"
];
const baseUrl =
"https://arcgis.github.io/arcgis-samples-javascript/sample-data/";
return fireflyImages.map(function(url, i) {
return {
value: i + 1, // Category number
symbol: {
type: "picture-marker",
url: baseUrl + url
}
}
});
}
function errorCallback(error) {
console.log("error:", error)
}
});
</script>
</head>
<body>
<div id="viewDiv">
<div id="info">
<span class="info">
<b>Populating grid...</b>
</span>
<br />
</div>
<div id="gridDisplay">
<span class="info" id="featureCount"></span>
<div id="grid"></div>
</div>
</div>
</body>
</html>
Lidia,
Have you seen this sample for 4.x that uses a dgrid?
Hi Robert, yes, I looked into this example. They use selection to set data variable and I don't need to select anything in my case. I am not sure how to replace that bit of the example.
I have just copied parts of the code to show what I mean.
const graphics = results.features;......
.........
const data = graphics.map(function(feature, i) {......
dataStore.objectStore.data = data;
grid.set("collection", dataStore);
I need data variable to be all features in the dataset... Any ideas on how to achieve it?
Thanks,
Lidia,
OK here is the sample re-worked to show all the data.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>dGrid - 4.9</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.9/esri/css/main.css">
<script src="https://js.arcgis.com/4.9/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
#info,
#gridDisplay {
position: absolute;
bottom: 0;
left: 0;
height: 35%;
background-color: white;
border-color: grey;
width: 100%;
font-family: "Avenir Next W00", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
}
#info {
z-index: 90;
font-size: 16px;
padding-left: 20px;
}
#info * {
padding-right: 20px;
}
#gridDisplay {
z-index: 80;
}
.info {
line-height: 20px;
padding-left: 5px ! important;
}
.dgrid-header,
.dgrid-header-row {
background-color: #eee;
color: #57585A;
}
.dgrid-row-even {
background-color: #F7F8F8;
}
.dgrid-row-odd {
background-color: #EFEFEF;
}
.dgrid-selected {
background: #B4DAF5;
}
.dgrid-row {
border: none
}
</style>
<script>
require([
"esri/WebMap",
"esri/views/MapView",
"esri/layers/CSVLayer",
"esri/Graphic",
"esri/widgets/Legend",
"esri/widgets/Expand",
"esri/geometry/SpatialReference",
"dgrid/OnDemandGrid",
"dgrid/extensions/ColumnHider",
"dojo/store/Memory",
"dstore/legacy/StoreAdapter",
"dgrid/Selection"
],
function(
WebMap, MapView, CSVLayer, Graphic, Legend, Expand, SpatialReference,
OnDemandGrid, ColumnHider, Memory, StoreAdapter, Selection
) {
let map, view, csvLayer, csvLayerView, grid;
const gridDiv = document.getElementById("grid");
const infoDiv = document.getElementById("info");
// create new map, view and csvlayer
setupTheView();
const gridFields = ["__OBJECTID", "Category", "Season", "Name",
"Nature", "wmo_wind"
];
// create a new datastore for the on demandgrid
// will be used to display attributes of selected features
const dataStore = new StoreAdapter({
objectStore: new Memory({
idProperty: "__OBJECTID"
})
});
// create a grid with given columns once the csvlayer is loaded
csvLayer.when(function() {
// create a grid with columns specified in gridFields variable
createGrid(csvLayer.fields);
// get a reference the csvlayerview when it is ready. It will used to do
// client side queries when user draws polygon to select features
view.whenLayerView(csvLayer).then(function(layerView) {
csvLayerView = layerView;
popGrid();
});
})
.catch(errorCallback);
/****************************************************
* Selects features from the csv layer that intersect
* a polygon that user drew using sketch view model
****************************************************/
function popGrid() {
view.graphics.removeAll();
if (csvLayerView) {
const query = {
where: "1=1",
outFields: ["*"]
};
// query graphics from the csv layer view. Geometry set for the query
// can be polygon for point features and only intersecting geometries are returned
csvLayerView.queryFeatures(query).then(function(results) {
const graphics = results.features;
// if the grid div is displayed while query results does not
// return graphics then hide the grid div and show the instructions div
if (graphics.length > 0) {
gridDiv.style.zIndex = 90;
infoDiv.style.zIndex = 80;
document.getElementById("featureCount").innerHTML =
"<b>Showing attributes for " +
graphics.length.toString() + " features </b>"
} else {
gridDiv.style.zIndex = 80;
infoDiv.style.zIndex = 90;
}
// get the attributes to display in the grid
const data = graphics.map(function(feature, i) {
return Object.keys(feature.attributes)
.filter(function(key) {
// get fields that exist in the grid
return (gridFields.indexOf(key) !== -1);
})
// need to create key value pairs from the feature
// attributes so that info can be displayed in the grid
.reduce(function(obj, key) {
obj[key] = feature.attributes[key];
return obj;
}, {});
});
// set the datastore for the grid using the
// attributes we got for the query results
dataStore.objectStore.data = data;
grid.set("collection", dataStore);
})
.catch(errorCallback);
}
}
/************************************************
* fires when user clicks a row in the grid
* get the corresponding graphic and select it
*************************************************/
function selectFeatureFromGrid(event) {
// close view popup if it is open
view.popup.close();
// get the ObjectID value from the clicked row
const row = event.rows[0]
const id = row.data.__OBJECTID;
// setup a query by specifying objectIds
const query = {
objectIds: [parseInt(id)],
outFields: ["*"],
returnGeometry: true,
outSpatialReference: view.SpatialReference
};
// query the csvLayerView using the query set above
csvLayerView.queryFeatures(query).then(function(results) {
const graphics = results.features;
// remove all graphics to make sure no selected graphics
view.graphics.removeAll();
view.goTo(graphics[0].geometry);
// create a new selected graphic
const selectedGraphic = new Graphic({
geometry: graphics[0].geometry,
symbol: {
type: "simple-marker",
style: "circle",
color: "orange",
size: "12px", // pixels
outline: { // autocasts as new SimpleLineSymbol()
color: [255, 255, 0],
width: 2 // points
}
}
});
// add the selected graphic to the view
// this graphic corresponds to the row that was clicked
view.graphics.add(selectedGraphic);
})
.catch(errorCallback);
}
/************************************************
* Creates a new grid. Loops through poverty
* csvLayer's fields and creates grid columns
* Grid with selection and columnhider extensions
*************************************************/
function createGrid(fields) {
var columns = fields.filter(function(field, i) {
if (gridFields.indexOf(field.name) !== -1) {
return field;
}
}).map(function(field) {
if (field.name === "__OBJECTID") {
return {
field: field.name,
label: field.name,
sortable: true,
hidden: true
};
} else {
return {
field: field.name,
label: field.alias,
sortable: true
};
}
});
// create a new onDemandGrid with its selection and columnhider
// extensions. Set the columns of the grid to display attributes
// the hurricanes cvslayer
grid = new(OnDemandGrid.createSubclass([Selection, ColumnHider]))({
columns: columns
}, "grid");
// add a row-click listener on the grid. This will be used
// to highlight the corresponding feature on the view
grid.on("dgrid-select", selectFeatureFromGrid);
}
/******************************************************
* Sets up the view. WebMap with winkel III projection
* basemap and hurricanes CsvLayer is added to the view.
******************************************************/
function setupTheView() {
const url =
"https://arcgis.github.io/arcgis-samples-javascript/sample-data/hurricanes.csv";
csvLayer = new CSVLayer({
title: "Hurricanes",
url: url,
spatialReference: new SpatialReference({wkid: 54042}),
copyright: "NOAA",
popupTemplate: {
title: "{Name}",
content: [{
type: "text",
text: "Category {Category} storm with that occurred at {ISO_time}."
}, {
type: "fields",
fieldInfos: [{
fieldName: "wmo_pres",
label: "Pressure"
}, {
fieldName: "wmo_wind",
label: "Wind Speed (mph)"
}]
}],
fieldInfos: [{
filedName: "ISO_time",
format: {
dateFormat: "short-date-short-time"
}
}]
},
renderer: {
type: "unique-value",
field: "Category",
uniqueValueInfos: createUniqueValueInfos()
}
});
map = new WebMap({
// contains a basemap with a Winkel III projection
// the CSVLayer coordinates will re-project client-side
portalItem: {
id: "7d127cef99a44327b79f5185602b8b6b"
},
layers: [csvLayer]
});
view = new MapView({
container: "viewDiv",
map: map,
highlightOptions: {
color: "#2B65EC",
fillOpacity: 0.4
},
padding: {
bottom: infoDiv.clientHeight
}
});
const legendExpand = new Expand({
view: view,
content: new Legend({
view: view,
style: "card"
})
});
view.ui.add(legendExpand, "top-left");
}
/*********************************************************
* Used to create uniques values for the csvlayer renderer
*********************************************************/
function createUniqueValueInfos() {
const fireflyImages = [
"cat1.png",
"cat2.png",
"cat3.png",
"cat4.png",
"cat5.png"
];
const baseUrl =
"https://arcgis.github.io/arcgis-samples-javascript/sample-data/";
return fireflyImages.map(function(url, i) {
return {
value: i + 1, // Category number
symbol: {
type: "picture-marker",
url: baseUrl + url
}
}
});
}
function errorCallback(error) {
console.log("error:", error)
}
});
</script>
</head>
<body>
<div id="viewDiv">
<div id="info">
<span class="info">
<b>Populating grid...</b>
</span>
<br />
</div>
<div id="gridDisplay">
<span class="info" id="featureCount"></span>
<div id="grid"></div>
</div>
</div>
</body>
</html>
Hi Robert,
Thank you. Great!
I have redone the code to use with my polygons and added ability to modify one field by entering values and writing it back to the feature layer.
Glad it worked. Don’t forget to mark this question as answered by clicking the mark correct link on the reply that answered your question.
Hi Robert,
I have added the links to the newest API 4.18 in the solution example:
<link rel="stylesheet" href="https://js.arcgis.com/4.18/dgrid/css/dgrid.css"/>
<link rel="stylesheet" href="https://js.arcgis.com/4.18/esri/css/main.css">
<script src="https://js.arcgis.com/4.18/"></script>
The problem is, the grid is not filled? Do you know why?
Greetings
Karsten
Try dropping back to 4.17. I am having issues with grid in 4.18 too.
The same result.
It looks like that it takes to long for querying for the grid. If I inset a setTimeout the grid get filled:
// create a grid with given columns once the csvlayer is loaded
csvLayer.when(function() {
// create a grid with columns specified in gridFields variable
createGrid(csvLayer.fields);
// get a reference the csvlayerview when it is ready. It will used to do
// client side queries when user draws polygon to select features
view.whenLayerView(csvLayer).then(function(layerView) {
csvLayerView = layerView;
console.log("timeout start");
setTimeout(function(){ popGrid(); }, 30000);
});
})
.catch(errorCallback);
Karsten,
OK I see what the issue is. The popGrid is getting called before the view has an features in it and because the code is set to query the layer view there are no records to return to the grid. If you change the code to query the actual csv layer and not the layer view then you get the grid with all records of the layer.
function popGrid() {
view.graphics.removeAll();
if (csvLayerView) {
const query = {
where: "1=1",
outFields: ["*"]
};
// query graphics from the csv layer view. Geometry set for the query
// can be polygon for point features and only intersecting geometries are returned
//Changed to csvLayer instead of layer view
csvLayer.queryFeatures(query).then(function(results) {
...