Take the 2-minute tour ×
Geographic Information Systems Stack Exchange is a question and answer site for cartographers, geographers and GIS professionals. It's 100% free, no registration required.

I'm creating a simple ArcGIS application that shows a table of data from a feature layer, one of which is a URL string. I'd like to make that string be a clickable hyperlink. Is this possible and if so what am I doing wrong. Below is my code. As you can see in the "UpdateGrid" function I'm creating a tag and inserting it into the cell. This is rendering fine and showing the URL but it's not clickable.

<!DOCTYPE html>
  <html>
  <head>
  <title>Stream Gauge Monitors</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <link rel="stylesheet" href="http://js.arcgis.com/3.7/js/dojo/dijit/themes/tundra/tundra.css">
  <link rel="stylesheet" type="text/css" href="http://js.arcgis.com/3.7/js/esri/css/esri.css">

  <style>
    html,body,#mapDiv,.map.container{
      padding:0;
      margin:0px;
      height:100%;
    }

    #legendDiv{
      background-color: #fff;
      position: absolute !important;
      z-index: 99;
      top:10px;
      right:20px;
      border-radius:20px;
    }

   body {
      overflow: hidden;
      overflow: hidden;
      font-family: "Roboto Condensed", sans-serif;
      font-size: 0.90em;
      margin: 0 auto;
      width: 100%;
    }

    #grid {
      height: 80%;
      width: 100%;
    }

    .field-NAME {
      width: 5%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-CURRENT_STAGE {
      width: 2%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-ALERT_STATUS {
      width: 2%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-LINK {
      width: 5%;
      font-size: .80em;
      font-weight: normal;
    }

    #grid .dgrid-row-odd {
      background: #F2F5F9;
    }

    .dgrid-no-data, .dgrid-loading {
      color: #aaa;
      font-size: 3em;
      padding: 3em;
      text-align: center;
    }
  </style>

  <script>var dojoConfig = { parseOnLoad: true };</script>
  <script src="http://js.arcgis.com/3.7compact/"></script>
  <script>
      var map,
          webmapId = "a8ca5a7d13b6458997352d82700e07c6",
          featureLayer, pageInfo, grid;

      require([
        "esri/map",
        "esri/arcgis/utils",
        "esri/dijit/Legend",
        "esri/layers/FeatureLayer",
        "esri/tasks/query",
        "esri/TimeExtent",
        "dojo/number",
        "dojo/date/locale",
        "dojo/dom",
        "dojo/on",
        "dojo/_base/array",
        "dojo/store/Memory",
        "dgrid/OnDemandGrid",
        "dojo/domReady!"
      ], function (Map, arcgisUtils, Legend, FeatureLayer, Query, TimeExtent,
        number, locale, dom, on,
        arrayUtils, Memory,
        OnDemandGrid) {
          arcgisUtils.createMap(webmapId, "mapDiv").then(function (response) {
              map = response.map

              var legend = new Legend({
                  map: map,
                  layerInfos: (arcgisUtils.getLegendLayers(response))
              }, "legendDiv");

              legend.startup();
          });

          var sortAttr = [{
              attribute: "name",
              descending: true
          }];
          grid = new OnDemandGrid({
              store: Memory({
                  idProperty: "OBJECTID"
              }),
              columns: {
                  NAME: "NAME",
                  CURRENT_STAGE: "CURRENT STAGE",
                  ALERT_STATUS: "ALERT STATUS",
                  LINK: "Link to Flood Data Image",
              },
              loadingMessage: "Loading data...",
              noDataMessage: "No results found.",
              sort: sortAttr
          }, "grid");

          // create a feature layer
          featureLayer = new FeatureLayer("http://geoweb.cityofsalem.net/salemgis/rest/services/Public/IT_StreamMonitoringStations/MapServer/0", {
              outFields: ["*"]
          });

          // get object IDs from the table (feature layer)
          featureLayer.on("load", function () {
              // create the query to fetch object IDs for earthquakes that have a magnitude greater than 6.0
              // that occurred after January 1st 2007
              var query = new Query();
              query.where = "OBJECTID > 0";
              featureLayer.queryIds(query, function (objectIds) {
                  fetchRecords(objectIds);
              });
          });

          // click listeners for prev/next page buttons
          on(dom.byId("prev"), "click", function () {
              queryRecordsByPage(pageInfo.currentPage - 1);
          });
          on(dom.byId("next"), "click", function () {
              queryRecordsByPage(pageInfo.currentPage + 1);
          });

          // formatting function for numbers
          function formatRound(value) {
              return number.round(value, 2);
          }

          // formatting function for dates
          function formatDate(value) {
              var inputDate = new Date(value);
              return locale.format(inputDate, {
                  selector: "date",
                  datePattern: "MMMM d, y"
              });
          }

          function fetchRecords(objectIds) {
              if (objectIds.length > 0) {
                  updatePageInformation(objectIds);
                  queryRecordsByPage(1);
              } else {
                  grid.showMessage("No matching records");
                  grid.setStore(null);
              }
          }

          function updatePageInformation(objectIds, page) {
              pageInfo = {
                  objectIds: objectIds,
                  totalRecords: objectIds.length,
                  totalPages: Math.ceil(objectIds.length / 15),
                  currentPage: page || 0,
                  recordsPerPage: 15
              };

              dom.byId("pageInfo").innerHTML = pageInfo.currentPage + "/" + pageInfo.totalPages;
              dom.byId("recordsInfo").innerHTML = pageInfo.totalRecords;

              if (pageInfo.currentPage > pageInfo.totalPages) {
                  queryRecordsByPage(pageInfo.currentPage - 1);
              }
          }

          function queryRecordsByPage(pageNumber) {
              // check if the page number is valid
              if (pageNumber < 1 || pageNumber > pageInfo.totalPages) {
                  return;
              }

              //grid.showMessage("Fetching records...");

              var begin = pageInfo.recordsPerPage * (pageNumber - 1);
              var end = begin + pageInfo.recordsPerPage;

              // create the query
              var query = new Query();
              query.objectIds = pageInfo.objectIds.slice(begin, end);
              query.outFields = ["*"];

              // Query for the records with the given object IDs and populate the grid
              featureLayer.queryFeatures(query, function (featureSet) {
                  updateGrid(featureSet, pageNumber);
              });
          }

          function updateGrid(featureSet, pageNumber) {

              var data = arrayUtils.map(featureSet.features, function (entry, i) {

                  var a = document.createElement('a');
                  var linkText = document.createTextNode(entry.attributes.LINK);
                  a.appendChild(linkText);
                  a.title =  entry.attributes.LINK;
                  a.href = entry.attributes.LINK;

                  return {
                      NAME: entry.attributes.NAME,
                      CURRENT_STAGE: entry.attributes.CURRENT_STAGE,
                      ALERT_STATUS: entry.attributes.ALERT_STATUS,
                      LINK: a
                  };
              });
              grid.store.setData(data);
              grid.refresh();

              // update application state
              pageInfo.currentPage = pageNumber;
              dom.byId("pageInfo").innerHTML = pageInfo.currentPage + "/" + pageInfo.totalPages;
          }

      });
  </script>

  </head>

  <body>
    <div id="mapDiv" style="height: 55%"></div>
    <div id="tbl" style="height:45%">

      <p>
  Total records: 
  <span id="recordsInfo">0</span>
  &nbsp;&nbsp;|&nbsp;&nbsp;

  <button id="prev">Prev Page</button>
  &nbsp;&nbsp<span id="pageInfo"></span>&nbsp;&nbsp

  <button id="next">Next Page</button>
</p>

<!-- dgrid will be created in this div -->
<div id="grid"></div>
    </div>
    <div id="legendDiv"></div>
  </body>
  </html>
share|improve this question

3 Answers 3

up vote 2 down vote accepted

When defining the columns for your grid, you can specify a formatting function to transform the value from the data store to something else. To do this, you need to modify your column definition and also define the function that will turn your URL into a link. Here's the relevant code:

grid = new OnDemandGrid({
    store: Memory({
        idProperty: "OBJECTID"
    }),
    columns: {
        NAME: "NAME",
        CURRENT_STAGE: "CURRENT STAGE",
        ALERT_STATUS: "ALERT STATUS",
        LINK: { label: "Link to Flood Data Image", formatter: makeLink }
    },
    loadingMessage: "Loading data...",
    noDataMessage: "No results found.",
    sort: sortAttr
}, "grid");

And the makeLink function:

function makeLink(data) {
  console.log("make link", data);
  return "<a target=\"_blank\" href=\"" + data + "\">" + data + "</a>";
}

Here's a full working version:

<!DOCTYPE html>
  <html>
  <head>
  <title>Stream Gauge Monitors</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
  <link rel="stylesheet" href="http://js.arcgis.com/3.7/js/dojo/dijit/themes/tundra/tundra.css">
  <link rel="stylesheet" type="text/css" href="http://js.arcgis.com/3.7/js/esri/css/esri.css">

  <style>
    html,body,#mapDiv,.map.container{
      padding:0;
      margin:0px;
      height:100%;
    }

    #legendDiv{
      background-color: #fff;
      position: absolute !important;
      z-index: 99;
      top:10px;
      right:20px;
      border-radius:20px;
    }

   body {
      overflow: hidden;
      overflow: hidden;
      font-family: "Roboto Condensed", sans-serif;
      font-size: 0.90em;
      margin: 0 auto;
      width: 100%;
    }

    #grid {
      height: 80%;
      width: 100%;
    }

    .field-NAME {
      width: 5%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-CURRENT_STAGE {
      width: 2%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-ALERT_STATUS {
      width: 2%;
      font-size: .80em;
      font-weight: normal;
    }

    .field-LINK {
      width: 5%;
      font-size: .80em;
      font-weight: normal;
    }

    #grid .dgrid-row-odd {
      background: #F2F5F9;
    }

    .dgrid-no-data, .dgrid-loading {
      color: #aaa;
      font-size: 3em;
      padding: 3em;
      text-align: center;
    }
  </style>

  <script>var dojoConfig = { parseOnLoad: true };</script>
  <script src="http://js.arcgis.com/3.7compact/"></script>
  <script>
      var map,
          webmapId = "a8ca5a7d13b6458997352d82700e07c6",
          featureLayer, pageInfo, grid;

      require([
        "esri/map",
        "esri/arcgis/utils",
        "esri/dijit/Legend",
        "esri/layers/FeatureLayer",
        "esri/tasks/query",
        "esri/TimeExtent",
        "dojo/number",
        "dojo/date/locale",
        "dojo/dom",
        "dojo/on",
        "dojo/_base/array",
        "dojo/store/Memory",
        "dgrid/OnDemandGrid",
        "dojo/dom-construct",
        "dojo/domReady!"
      ], function (Map, arcgisUtils, Legend, FeatureLayer, Query, TimeExtent,
        number, locale, dom, on,
        arrayUtils, Memory,
        OnDemandGrid, domConstruct) {
          arcgisUtils.createMap(webmapId, "mapDiv").then(function (response) {
              map = response.map

              var legend = new Legend({
                  map: map,
                  layerInfos: (arcgisUtils.getLegendLayers(response))
              }, "legendDiv");

              legend.startup();
          });

          var sortAttr = [{
              attribute: "name",
              descending: true
          }];
          grid = new OnDemandGrid({
              store: Memory({
                  idProperty: "OBJECTID"
              }),
              columns: {
                  NAME: "NAME",
                  CURRENT_STAGE: "CURRENT STAGE",
                  ALERT_STATUS: "ALERT STATUS",
                  LINK: { label: "Link to Flood Data Image", formatter: makeLink }
              },
              loadingMessage: "Loading data...",
              noDataMessage: "No results found.",
              sort: sortAttr
          }, "grid");

          // create a feature layer
          featureLayer = new FeatureLayer("http://geoweb.cityofsalem.net/salemgis/rest/services/Public/IT_StreamMonitoringStations/MapServer/0", {
              outFields: ["*"]
          });

          // get object IDs from the table (feature layer)
          featureLayer.on("load", function () {
              // create the query to fetch object IDs for earthquakes that have a magnitude greater than 6.0
              // that occurred after January 1st 2007
              var query = new Query();
              query.where = "OBJECTID > 0";
              featureLayer.queryIds(query, function (objectIds) {
                  fetchRecords(objectIds);
              });
          });

          // click listeners for prev/next page buttons
          on(dom.byId("prev"), "click", function () {
              queryRecordsByPage(pageInfo.currentPage - 1);
          });
          on(dom.byId("next"), "click", function () {
              queryRecordsByPage(pageInfo.currentPage + 1);
          });

          // formatting function for numbers
          function formatRound(value) {
              return number.round(value, 2);
          }

          // formatting function for dates
          function formatDate(value) {
              var inputDate = new Date(value);
              return locale.format(inputDate, {
                  selector: "date",
                  datePattern: "MMMM d, y"
              });
          }

          function fetchRecords(objectIds) {
              if (objectIds.length > 0) {
                  updatePageInformation(objectIds);
                  queryRecordsByPage(1);
              } else {
                  grid.showMessage("No matching records");
                  grid.setStore(null);
              }
          }

          function updatePageInformation(objectIds, page) {
              pageInfo = {
                  objectIds: objectIds,
                  totalRecords: objectIds.length,
                  totalPages: Math.ceil(objectIds.length / 15),
                  currentPage: page || 0,
                  recordsPerPage: 15
              };

              dom.byId("pageInfo").innerHTML = pageInfo.currentPage + "/" + pageInfo.totalPages;
              dom.byId("recordsInfo").innerHTML = pageInfo.totalRecords;

              if (pageInfo.currentPage > pageInfo.totalPages) {
                  queryRecordsByPage(pageInfo.currentPage - 1);
              }
          }

          function queryRecordsByPage(pageNumber) {
              // check if the page number is valid
              if (pageNumber < 1 || pageNumber > pageInfo.totalPages) {
                  return;
              }

              //grid.showMessage("Fetching records...");

              var begin = pageInfo.recordsPerPage * (pageNumber - 1);
              var end = begin + pageInfo.recordsPerPage;

              // create the query
              var query = new Query();
              query.objectIds = pageInfo.objectIds.slice(begin, end);
              query.outFields = ["*"];

              // Query for the records with the given object IDs and populate the grid
              featureLayer.queryFeatures(query, function (featureSet) {
                  updateGrid(featureSet, pageNumber);
              });
          }

          function updateGrid(featureSet, pageNumber) {

              var data = arrayUtils.map(featureSet.features, function (entry, i) {
                  return {
                      NAME: entry.attributes.NAME,
                      CURRENT_STAGE: entry.attributes.CURRENT_STAGE,
                      ALERT_STATUS: entry.attributes.ALERT_STATUS,
                      LINK: entry.attributes.LINK
                  };
              });
              grid.store.setData(data);
              grid.refresh();

              // update application state
              pageInfo.currentPage = pageNumber;
              dom.byId("pageInfo").innerHTML = pageInfo.currentPage + "/" + pageInfo.totalPages;
          }

          function makeLink(data) {
            console.log("make link", data);
            return "<a target=\"_blank\" href=\"" + data + "\">" + data + "</a>";
          }

      });
  </script>

  </head>

  <body>
    <div id="mapDiv" style="height: 55%"></div>
    <div id="tbl" style="height:45%">

      <p>
  Total records: 
  <span id="recordsInfo">0</span>
  &nbsp;&nbsp;|&nbsp;&nbsp;

  <button id="prev">Prev Page</button>
  &nbsp;&nbsp<span id="pageInfo"></span>&nbsp;&nbsp

  <button id="next">Next Page</button>
</p>

<!-- dgrid will be created in this div -->
<div id="grid"></div>
    </div>
    <div id="legendDiv"></div>
  </body>
  </html>
share|improve this answer
    
This is fantastic, works perfectly and will be applicable to several other issues I have. Awesome work. –  Neberu Nov 21 '13 at 16:54
    
Glad I could help! –  Derek Swingley Nov 22 '13 at 4:49

I have a different circumstance where I am dynamically building several dGrids to contain the results of multiple IdentifyTasks on the visible layers in my map (which are in multiple services). Occasionally, there will be fields that contain hyperlinks, so I was happy to come across this thread.

From my array of results (contain the promises from each IdentifyTask), I build the columns array by using the first feature in each result.

function buildColumns(feature) {

    var attributes = feature.attributes;
    var columns = [];
    for (var attribute in attributes) {
        if (attributes.hasOwnProperty(attribute)) {
            var objects = {};
            objects.label = attribute;
            objects.field = attribute;
            if (attribute === "Shape") {
                objects.hidden = true;
            }

            if (attributes[attribute].indexOf("http://") === 0) {
                objects.formatter = makeLink;
            }
            columns.push(objects);
        }
    }
    return columns;
}

The makeLink function is the same as in @Derek's code.

This is a portion of the code showing how I cycle through the results array to build each grid and put it into a ContentPane

        for (var result in combineResults) {
            if (combineResults.hasOwnProperty(result)) {

                var columns = buildColumns(combineResults[result][0]);
                var features = [];

                for (i = 0, len = combineResults[result].length; i < len; i++) {
                    features.push(combineResults[result][i]);
                }

                var data = array.map(features, function (feature) {
                    return lang.clone(feature.attributes);
                });

                var dataGrid = new (declare([Grid, Selection, DijitRegistry, ColumnHider]))({
                    bufferRows: Infinity,
                    columns: columns,
                    selectionMode: "single",
                    "class": "resultsGrid"
                });

                //This makes each column 100 px wide 
                var gridWidth = "width: " + String(columns.length * 100) + "px";
                dataGrid.addCssRule("#" + dataGrid.id, gridWidth);

                //This will flash the selected feature on the map when the row is clicked
                dataGrid.on(".dgrid-row:click", gridSelect);
                dataGrid.on("show", function () {
                    dataGrid.resize();
                });
                //This highlights the feature when the mouse enters and leaves the row
                dataGrid.on(mouseUtil.enterRow, gridEnter);
                dataGrid.on(mouseUtil.leaveRow, function () {
                    map.graphics.clear();
                });
                var plural = "";
                if (combineResults[result].length !== 1) { plural = "s"; }
                var cp = new ContentPane({
                    id: result,
                    content: combineResults[result].length + " feature" + plural,
                    title: combineResults[result][0].layerName,
                    style: "overflow: auto"
                }).placeAt(registry.byId('tabs'));

                cp.addChild(dataGrid);
                cp.startup();

                dataGrid.renderArray(data);
            }

A previous version of this code is used here.

share|improve this answer

The answers by @kenbuja and @Derek Swingley work when you know your data set to start with or which columns will contain links. But if you are hooking up to something like a jsonRestStore you can't parse the data before hand and you it may be possible all columns could contain links.

Far better to use the renderCell function which gives you access to the dom node and value at the point the grid is rendered. If you mix in this extension it will render the cell as a hyperlink for you.

e.g.

var grid = declare([OnDemandGrid, dGridHyperlink])

dGridHyperlink.js

define([
        "dojo/_base/declare",
        "dojo/_base/lang",
        "dojo/dom-construct"


],
    function (declare, lang, domConstruct,
       ) {


        return declare([], {

            //this class will format cells as a hyperlink if they start with http:// https:// or file:\\
            //It will only work if a field does not have a formatter or a renderCell function set on it already


            //sub fired on the grid startup or when columns are set
            _configColumns: function (prefix, rowColumns) {

                try {

                    if (rowColumns) {
                        for (var i = 0; i < rowColumns.length; i++) {
                            //check if there is already a formatter or renderCell defined in the column set as this will override it
                            if (!rowColumns[i].formatter && !rowColumns[i].renderCell) {
                                rowColumns[i].renderCell = this.renderHyperlink;
                            }
                        }

                    };
                } catch (e) {
                    console.error("error configuring columns");
                }
                return this.inherited(arguments);
            },

            renderHyperlink: function (object, value, node, options) {
                if (!value) return null;
                var sValue = value.toString().toLowerCase();
                if (sValue.slice(0, 7) == 'http://' || sValue.slice(0, 8) == 'https://' || sValue.slice(0, 7) == 'file://') {
                    var div = domConstruct.create("div", { 'class': 'dGridHyperlink', 'innerHTML': '<a target=\"_blank\" href=\"' + sValue + '"\">' + sValue + '</a>' });
                    return div;
                } else {
                    return node.innerHTML = value;
                }
            }

        });
    });
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.