[GIS] Make InfoTemplate moveable but still highlight attributes

arcgis-javascript-apidojojavascriptjquery

I’m using the esri/InfoTemplate in the Javascript API to identify features. However, it gets anchored to the mouse click and cannot be moved around.

Based on this answer I used dojo/dnd/Moveable to make it moveable.

However, now I cannot highlight or copy the attributes inside of it. It’s like Moveable turns it into a graphic or something.

Is there a way to make the InfoTemplate floating but still be able to highlight and copy the attributes?

Thanks in advance for your help.

(The other option I’m considering is using the jQuery Dialog Widget, but I cannot get it to highlight the geometry for multiple features. Any suggestions there would be great too. Whichever is easier.)

Best Answer

I really appreciate all the suggestions, but I never got the esri/dijit/Popup to be moveable and still be able to highlight and copy the text inside. Instead I used a jQuery Dialog, which I figured would be the case but it just took a while to get all the experience needed to make it work the way I wanted it to.

My boss and I both worked on it and we still don't fully understand everything happening. But it works and that's all that matters to us.

Here’s what it looks like with jQuery theme “smoothness”.

enter image description here

Here is the basic sum up:

Since the inner content of a jQuery Dialog needs to be a div, I had to make an array of div elements with the identified attributes as the innerHTML.

  • When the map is clicked, the code cycles through the visible features. (I can’t remember where I got that from but it looks a lot like this http://jsfiddle.net/blordcastillo/mULcz/)

  • As it’s cycling through, a div is created for the attributes of each feature and the div is given a custom geometry attribute which is set to the geometry of the feature.

  • “Previous” and “next” buttons cycle through the array of divs and fires a function to draw the geometry on the map.

  • Using the geometry attribute of each div, a "Zoom to" button is added for zooming to the feature.

Basically, it acts the same as the Popup and InfoTemplate from the esri api, but now it is moveable around the screen and I can still interact with the text (highlight and copy).

Below is a copy of my module (cut down for size).

Feel free to either use it or “fork” it and let me know how to make it better. Hopefully it helps others.

Thanks again to everybody for all the suggestions!

define(["dojo/_base/declare", "dojo/dom", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/Deferred", "dojo/DeferredList",
    "dojo/_base/Color", "dijit/registry",
    //esri
    "esri/tasks/IdentifyParameters", "esri/tasks/IdentifyTask", "esri/geometry/Extent",
    "esri/geometry/Point", "esri/graphic", "esri/SpatialReference", "esri/tasks/ProjectParameters",
    "esri/symbols/PictureMarkerSymbol", "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol"
], function (declare, dom, lang, array, Deferred, DeferredList, Color, registry,
    IdentifyParameters, IdentifyTask, Extent, Point, Graphic, SpatialReference, ProjectParameters, PictureMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol) {

var $dialogDiv = $('#dialogDiv');
var featureArray = [];
var identifyParams = new IdentifyParameters();
identifyParams.tolerance = 5;
identifyParams.returnGeometry = true;
identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_VISIBLE;

//point, line and polygon symbology for the DrawGeometry() function.
var ptSymbol = new PictureMarkerSymbol({
        "angle" : 0,
        "xoffset" : 0,
        "yoffset" : 0,
        "type" : "esriPMS",
        "url" : "http://" + server + "/apps/references/images/PointID32.png",
        "contentType" : "image/png",
        "width" : 24,
        "height" : 24
    });
var lineSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID, new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([0, 255, 255]), 3), null);
var fillSymbol = new SimpleFillSymbol(SimpleFillSymbol.STYLE_SOLID, new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.25]));

return declare([], {

    executeIdentifyTask : function (evt) {
        var mapPoint = evt.mapPoint;
        myMap.setMapCursor("wait");
        idGL.clear();
        if (typeof dom.byId("divThing") !== "undefined" || dom.byId("divThing") !== null) {
            $("#divThing").remove();
        }
        var counter = 0;
        EmptyArray(featureArray);

        //I think this stuff came from here
        //http://jsfiddle.net/blordcastillo/mULcz/

        var layers = array.map(myMap.layerIds, function (layerId) {
                return myMap.getLayer(layerId);
            }); //Create an array of all layers in myMap
        layers = array.filter(layers, function (layer) {
                return layer.getImageUrl && layer.visible;
            }); //Only dynamic layers have the getImageUrl function. Filter so you only query visible dynamic layers
        var tasks = array.map(layers, function (layer) {
                return new IdentifyTask(layer.url);
            }); //myMap each visible dynamic layer to a new identify task, using the layer url. Set identifiable layerIds per service.
        var defTasks = array.map(tasks, function () {
                return new Deferred();
            }); //myMap each identify task to a new dojo.Deferred

        var dlTasks = new DeferredList(defTasks); //And use all of these Deferreds in a DeferredList
        dlTasks.then(showResults); //chain showResults onto your DeferredList/
        identifyParams.width = myMap.width;
        identifyParams.height = myMap.height;
        identifyParams.geometry = evt.mapPoint;
        identifyParams.mapExtent = myMap.extent;

        var i;
        for (i = 0; i < tasks.length; i++) { //Use 'for' instead of 'for...in' so you can sync tasks with defTasks
            try {
                //These are the services in the app.
                if (tasks[i].url === addressURL) {
                    identifyParams.layerIds = strucPts.visibleLayers; // [1,2,5,6];
                }
                if (tasks[i].url === transportationURL) {
                    identifyParams.layerIds = transPts.visibleLayers; //identifyParams.layerIds = [1, 2, 4, 5, 6];
                }
                if (tasks[i].url === cadastralURL) {
                    identifyParams.layerIds = cadasPoly.visibleLayers; //[0,1,3];
                }
                // Etc for all services
                tasks[i].execute(identifyParams, defTasks[i].callback, defTasks[i].errback); //Execute each task
            } catch (e) {
                console.log("Error caught");
                console.log(e);
                defTasks[i].errback(e); //If you get an error for any task, execute the errback
            }
        }
        function showResults(r) {

            myMap.setMapCursor("help");

            var results = [];
            r = array.filter(r, function () {
                    return r[0];
                }); //filter out any failed tasks

            for (i = 0; i < r.length; i++) {
                results = results.concat(r[i][1]);
            }

            results = array.map(results, function (result) {
                    var feature = result.feature;
                    feature.attributes.layerName = result.layerName;

                    ////ADDRESSING
                    if (result.layerName === "Address") {
                        var geom = feature.geometry;
                        var content = "";
                        if (lang.trim(feature.attributes.ADDRESS) !== "" && lang.trim(feature.attributes.ADDRESS) !== "Null") {
                            content += "<strong>Property Address:</strong> " + lang.trim(feature.attributes.ADDRESS) + "<br>";
                        }
                        if (lang.trim(feature.attributes.STREET) !== "" && lang.trim(feature.attributes.STREET) !== "Null") {
                            content += "<strong>Street:</strong> " + lang.trim(feature.attributes.STREET) + "<br>";
                        }
                        //Etc for all attributes
                        if (content === "" || content === "Null") {
                            content = "No information available at this time.";
                        }
                        CreateFeatureDiv(content, geom, result.layerName, featureArray);
                    }
                    ////CADASTRAL
                    if (result.layerName === "Cadastral") {
                        var geom = feature.geometry;
                        var content = "";
                        //Same if statements as above
                        CreateFeatureDiv(content, geom, result.layerName, featureArray);
                    }
                    ////TRANSPORTATION
                    if (result.layerName == 'Major Roads' || result.layerName == 'Local Roads') {
                        var geom = feature.geometry;
                        //Same if statements as above
                        CreateFeatureDiv(content, geom, result.layerName, featureArray);
                    }
                    //Etc, etc, etc for all layers in the map
                });

            if (typeof featureArray[counter] === "undefined" || featureArray[counter] === null) {
                $dialogDiv.dialog("close");
                return;
            }
            var divThing = document.createElement("div");
            divThing.id = "divThing";
            divThing.innerHTML = "<br>" + featureArray[counter].innerHTML;
            $dialogDiv.append(divThing);
            InsertZoomButton(divThing, featureArray[counter].geometry);
            DrawGeometry(featureArray[counter].geometry);
            EnableDisable(counter, featureArray);

            //// http://stackoverflow.com/questions/16549183/jquery-iterate-through-an-array-by-using-onclick
            dom.byId('prevFeature').onclick = function () {
                if (featureArray.length === 1) {
                    return;
                } else if (counter <= 0) {
                    counter = 0;
                    return;
                } else if (counter > 0) {
                    counter--;
                    FeatureClick(counter);
                } else {
                    return;
                }
            }
            dom.byId('nextFeature').onclick = function () {
                if (featureArray.length === 1) {
                    return;
                } else if (counter >= featureArray.length - 1) {
                    counter = featureArray.length - 1;
                    return;
                } else if (counter < featureArray.length - 1) {
                    counter++;
                    FeatureClick(counter);
                } else {
                    return;
                }
            }
            function FeatureClick(counter) {
                $("#divThing").remove();
                if (typeof featureArray[counter] === "undefined" || featureArray[counter] === null) {
                    return;
                }
                var feature = featureArray[counter];
                var divThing = document.createElement("div");
                divThing.id = "divThing";
                divThing.innerHTML = "<br>" + feature.innerHTML;
                var next = counter + 1;
                $dialogDiv.dialog('option', 'title', feature.title + " (" + next + " of " + featureArray.length + ")");
                $dialogDiv.append(divThing);
                DrawGeometry(feature.geometry);
                InsertZoomButton(divThing, feature.geometry);
                EnableDisable(counter, featureArray);
            }

            var place = counter + 1;
            $dialogDiv.dialog({
                title : featureArray[0].title + " (" + place + " of " + featureArray.length + ")",
                minWidth : 200,
                maxWidth : 800,
                minHeight : 300,
                maxHeight : 500,
                close : function () {
                    idGL.clear();
                    EmptyArray(featureArray);
                },
                show : 'fold',
                hide : 'fold',
                resizable : true
            });

            $dialogDiv.dialog("open");
        }
    }
});
function CreateFeatureDiv(htmlContent, geometry, featureName, array) {
    var div = document.createElement("div");
    div.innerHTML = htmlContent + "<br>";
    div["geometry"] = geometry;
    div["title"] = featureName;
    array.push(div);
}
function DrawGeometry(geometry) {
    switch (geometry.type) {
    case "point":
        var pt = new Point(geometry.x, geometry.y);
        var gr = new Graphic(pt, ptSymbol);
        idGL.clear();
        idGL.add(gr);
        break;
    case "polyline":
        var graphic = new Graphic(geometry, lineSymbol);
        idGL.clear();
        idGL.add(graphic);
        break;
    case "polygon":
        var graphic = new Graphic(geometry, fillSymbol);
        idGL.clear();
        idGL.add(graphic);
        break;
    default:
        return;
    }
}
function InsertZoomButton(div, geometry) {
    var btnZoom = document.createElement("button");
    var t = document.createTextNode("Zoom to");
    btnZoom.appendChild(t);
    div.appendChild(btnZoom);
    btnZoom.onclick = function () {
        switch (geometry.type) {
        case "point":
            var pt = new Point(geometry.x, geometry.y, myMap.spatialReference);
            myMap.setScale(2000);
            myMap.centerAt(pt);
            break;
        case "polyline":
            var findExtent = geometry._extent;
            var extent = new Extent({
                    "xmax" : findExtent.xmax,
                    "xmin" : findExtent.xmin,
                    "ymax" : findExtent.ymax,
                    "ymin" : findExtent.ymin,
                    "spatialReference" : {
                        "wkid" : 2243
                    }
                });
            myMap.setExtent(extent);
            break;
        case "polygon":
            var findExtent = geometry._extent;
            var extent = new Extent({
                    "xmax" : findExtent.xmax,
                    "xmin" : findExtent.xmin,
                    "ymax" : findExtent.ymax,
                    "ymin" : findExtent.ymin,
                    "spatialReference" : {
                        "wkid" : 2243
                    }
                });
            myMap.setExtent(extent);
            break;
        default:
            return;
        }
    };
}
function EmptyArray(array) {
    if (array.length > 0) {
        while (array.length > 0) {
            array.pop();
        }
    }
}
function EnableDisable(counter, array) {
    if (counter === 0 && counter === array.length - 1) {
        DisablePrev();
        DisableNext();
    } else if (counter === 0 && counter < array.length - 1) {
        DisablePrev();
        EnableNext();
    } else if (counter > 0 && counter < array.length - 1) {
        EnablePrev();
        EnableNext();
    } else if (counter > 0 && counter === array.length - 1) {
        EnablePrev();
        DisableNext();
    } else {
        console.log("Else returned");
    }
}
function DisablePrev() {
    registry.byId("prevFeature").set("iconClass", "prevFeature_gray");
    registry.byId("prevFeature").set("disabled", true);
}
function EnablePrev() {
    registry.byId("prevFeature").set("iconClass", "prevFeature");
    registry.byId("prevFeature").set("disabled", false);
}
function DisableNext() {
    registry.byId("nextFeature").set("iconClass", "nextFeature_gray");
    registry.byId("nextFeature").set("disabled", true);
}
function EnableNext() {
    registry.byId("nextFeature").set("iconClass", "nextFeature");
    registry.byId("nextFeature").set("disabled", false);
}

});
Related Question