Skip to content

Instantly share code, notes, and snippets.

@asizer
Last active August 29, 2015 14:00
Show Gist options
  • Save asizer/ca95c01155b209d9b17e to your computer and use it in GitHub Desktop.
Save asizer/ca95c01155b209d9b17e to your computer and use it in GitHub Desktop.
Layer Search

Configurable attribute search for FeatureServices and MapServices. Styled after the [arcgis-geocoder-dijit] (https://github.com/Esri/arcgis-dijit-geocoder-js).

The constructor takes an array of layerSearcher config objects. These config objects vary slightly for FeatureServices and MapServices.

Common properties:

{
	    name: 'Search fire names in wildfire activity',  // name for dropdown menu
	    placeholder: 'Find fires',                       // for empty input box
	    qLayerId: 'fireLayer',                           // layer to query
	    qFields: ['FIRE_NAME', 'Fire name'],             // field(s) to search on. 
	    qLabelFunction: function(feat, lyrNum) {         // returns string for autocomplete dropdown
	        return feat.FIRE_NAME + ' fire in ' + feat.STATE;
	    })
	    .
	    .
	    .

Additional properties for a FeatureServices:

	    .
	    .
	    .
	    qFields: ['FIRE_NAME', 'Fire name'],   // REQUIRED
	    qOIDField: 'OBJECTID',                 // a SINGLE STRING
	    qOutfields: ['STATE']                  // must include any fields needed in the qLabelFunction besides the qFields and qOIDField above. 
 }

Additional properties for a MapServices:

	    .
	    .
	    .
	    qFields: ['FIRE_NAME', 'Fire name'],            // OPTIONAL. If not present, searches ALL fields
	    qLayerLayers: [0, 1, 3],                        // sublayer(s) to search on. REQUIRED.
	    qOIDField: ['OBJECTID' 'ObjectId', 'ObjectId1'], // an array of strings in the same order as the qLayerLayers above. 
	    qLabelFunction: function(feat, lyrNum) {}       // notice this function has the sublayer number as an argument, which can be used to decide the appropriate label for the feature.
 }
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=7, IE=9, IE=10">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>ArcGIS API for JavaScript | Simple Layer Searching</title>
<link rel="stylesheet" type="text/css" href="//js.arcgis.com/3.9/js/esri/css/esri.css" />
<link rel="stylesheet" type="text/css" href="LayerSearch.css">
<style>
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
#search {
display: block;
position: absolute;
z-index: 2;
top: 20px;
left: 74px;
}
</style>
<script type="text/javascript">
var package_path = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'));
var dojoConfig = {
// The locationPath logic below may look confusing but all its doing is
// enabling us to load the api from a CDN and load local modules from the correct location.
packages: [{
name: "application",
location: package_path
}]
};
</script>
<script src="//js.arcgis.com/3.9"></script>
<script>
var map, layersearch;
require([
"esri/map",
"application/LayerSearch",
"dojo/on",
"esri/symbols/SimpleMarkerSymbol",
"dojo/_base/Color",
"esri/InfoTemplate",
"esri/graphic",
"esri/geometry/Extent",
"esri/geometry/Multipoint",
"esri/geometry/Polygon",
"esri/geometry/Polyline",
"esri/geometry/ScreenPoint",
"esri/layers/FeatureLayer",
"esri/layers/ArcGISDynamicMapServiceLayer",
"dojo/domReady!"
], function (
Map,
LayerSearch,
on,
SimpleMarkerSymbol,
Color,
InfoTemplate,
Graphic,
Extent,
Multipoint,
Polygon,
Polyline,
ScreenPoint,
FeatureLayer,
AGSDMSLayer
) {
map = new Map("map", {
basemap: "gray",
center: [-120.435, 46.159], // lon, lat
zoom: 7
});
map.addLayer(new AGSDMSLayer('http://tmservices1.esri.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer/', {
id: 'fireLayer'
}));
var windLayer = map.addLayer(new FeatureLayer('http://tmservices1.esri.com/arcgis/rest/services/LiveFeeds/NOAA_METAR_current_wind_speed_direction/MapServer/0/', {
outFields: ['WIND_DIRECT'],
id: 'windLayer'
}));
windLayer.on('load', function(response) {
response.layer.renderer.setRotationInfo({
field: 'WIND_DIRECT',
type: 'geographic'
});
});
var layerSearchers = setupLayerSearchers();
var lsOptions = setupLSOptions(layerSearchers);
layersearch = new LayerSearch(lsOptions, "search");
layersearch.startup();
// on search results
on(layersearch, 'find-results', function (results) {
console.log('search', results);
});
// on search results
on(layersearch, 'featuresearch-select', function (results) {
console.log('onselect', results);
});
// on search results
on(layersearch, 'auto-complete', function (results) {
console.log('autocomplete', results);
});
// on params
on(layersearch, 'select', function (results) {
console.log('params', results);
});
layersearch.on('load', function () {
});
function setupLayerSearchers() {
return [{
name: 'Search fire names in wildfire activity', // name for dropdown menu
placeholder: 'Find fires',
qLayerId: 'fireLayer',
//qFields: ['FIRE_NAME', 'Fire name'], // field(s) to search on. if blank on a dynamic map service, it searches all fields. if blank on a feature layer, it doesn't search.
qLayerLayers: [0, 1, 3], // sublayer(s) to search on for dynamic map service. do not include this param for a feature layer
qOIDField: ['OBJECTID', 'OBJECTID', 'OBJECTID'], // need objectId field for each of the layers.
//qOutfields: ['STATE'], // any fields needed below besides qFields and qOIDField. only necessary for featureLayer. ignored for dynamicMapService.
qLabelFunction: function(feat, lyrNum) { // returns string for autocomplete dropdown. in a featureLayer, all fields used below must be in the qFields, qOIDField, and qOutfields options above -- otherwise those attributes won't be returned with the feature query.
switch (lyrNum) {
case 0:
case 1:
return (feat.FIRE_NAME || feat['Fire name']) + ' fire in ' + (feat.STATE || feat.State);
case 3:
return feat.FIRE_NAME + ' fire perimeter, ' + feat.UNIT_ID;
default:
return 'Unknown fire name';
}
}
}, {
name: 'Search station names in windspeed reports',
placeholder: 'Find stations',
qLayerId: 'windLayer',
qFields: ['STATION_NAME'],
qOIDField: 'OBJECTID',
qOutfields: ['COUNTRY'],
qLabelFunction: function(feat) { return feat.STATION_NAME + ' station in ' + feat.COUNTRY; }
}];
}
function setupLSOptions(lsArr) {
return {
map: map,
minCharacters: 3,
autoNavigate: true,
autoComplete: true,
theme: "invertedLayerSearcher",
layerSearchers: lsArr
};
}
});
</script>
</head>
<body>
<div id="search"></div>
<div id="map"></div>
</body>
</html>
.esriLayerSearcher .esriLayerSearcherClearFloat {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
.simpleLayerSearcher .esriLayerSearcherContainer *,
.invertedLayerSearcher .esriLayerSearcherContainer {
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
box-sizing: content-box;
}
.simpleLayerSearcher .esriLayerSearcherContainer,
.invertedLayerSearcher .esriLayerSearcherContainer {
width: 225px;
font-size: 12px;
line-height: 16px;
font-family: verdana,helvetica;
position: relative;
}
.simpleLayerSearcher .esriLayerSearcherContainer ul,
.invertedLayerSearcher .esriLayerSearcherContainer ul {
margin: 0;
padding: 0;
list-style: none;
display: block;
color: #444;
}
.invertedLayerSearcher .esriLayerSearcherContainer ul {
color: #bbb;
}
.simpleLayerSearcher .esriLayerSearcherIcon,
.invertedLayerSearcher .esriLayerSearcherIcon {
float: left;
outline: 0;
width: 16px;
height: 16px;
display: block;
overflow: hidden;
margin: 6px 0 6px 6px;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcherIcon,
.dj_rtl .invertedLayerSearcher .esriLayerSearcherIcon {
float: right;
margin: 6px 6px 6px 0;
}
.esriLayerSearcher .esriLayerSearcherClearFloat {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
.simpleLayerSearcher .esriLayerSearcher,
.invertedLayerSearcher .esriLayerSearcher {
display: block;
width: 100%;
margin: 0;
border: 1px solid #57585A;
background: #fff;
-webkit-border-radius: 5px;
border-radius: 5px;
}
.invertedLayerSearcher .esriLayerSearcher {
border: 1px solid #A8A7A5;
background: #333;
}
.simpleLayerSearcher .esriLayerSearcherActive,
.simpleLayerSearcher .esriLayerSearcherMenuActive,
.invertedLayerSearcher .esriLayerSearcherActive,
.invertedLayerSearcher .esriLayerSearcherMenuActive {
border-bottom: 0;
-webkit-border-radius: 5px 5px 0 0;
border-radius: 5px 5px 0 0;
}
.simpleLayerSearcher .esriLayerSearcher input,
.invertedLayerSearcher .esriLayerSearcher input {
outline: 0;
display: block;
border: 0;
border-collapse: collapse;
vertical-align: middle;
font-size: 12px;
line-height: 16px;
margin: 0;
padding: 6px 0;
float: left;
color: #444;
background: none;
margin: 0 6px;
width: 160px;
}
.invertedLayerSearcher .esriLayerSearcher input {
color: #bbb;
background: #333;
}
.simpleLayerSearcher .esriLayerSearcherMultiple input,
.invertedLayerSearcher .esriLayerSearcherMultiple input {
width: 146px;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcher input,
.dj_rtl .invertedLayerSearcher .esriLayerSearcher input {
float: right;
}
.simpleLayerSearcher .esriLayerSearcher input:focus {
color: #333;
}
.invertedLayerSearcher .esriLayerSearcher input:focus {
color: #ccc;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch {
cursor: pointer;
margin-left: 6px;
background: url(simpleLayerSearcher.png) no-repeat 0 0;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch,
.dj_rtl .invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch {
margin-left: 0;
margin-right: 6px;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch:hover,
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch:focus,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch:hover,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherSearch:focus {
opacity: .75;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherReset,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherReset {
margin: 6px 6px 6px 0;
float: right;
display: none;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherReset,
.dj_rtl .invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherReset {
margin: 6px 0 6px 6px;
float: left;
}
.simpleLayerSearcher .esriLayerSearcherHasValue .esriLayerSearcherReset,
.invertedLayerSearcher .esriLayerSearcherHasValue .esriLayerSearcherReset {
cursor: pointer;
display: block;
background: url(simpleLayerSearcher.png) no-repeat -48px 0;
}
.invertedLayerSearcher .esriLayerSearcherHasValue .esriLayerSearcherReset {
background: url(invertedLayerSearcher.png) no-repeat -64px 0;
}
.simpleLayerSearcher .esriLayerSearcherLoading .esriLayerSearcherReset {
background: url(loading.gif) center center no-repeat;
}
.invertedLayerSearcher .esriLayerSearcherLoading .esriLayerSearcherReset {
background: url(invertedLoading.gif) center center no-repeat;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherReset:hover,
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherReset:focus,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherReset:hover,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherReset:focus {
opacity: .75;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow {
display: none;
cursor: pointer;
background: url(simpleLayerSearcher.png) no-repeat -32px 0;
}
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow {
background: url(invertedLayerSearcher.png) no-repeat -32px 0;
}
.simpleLayerSearcher .esriLayerSearcherMultiple .esriLayerSearcherMenuArrow,
.invertedLayerSearcher .esriLayerSearcherMultiple .esriLayerSearcherMenuArrow {
display: block;
}
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow:hover,
.simpleLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow:focus,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow:hover,
.invertedLayerSearcher .esriLayerSearcher .esriLayerSearcherMenuArrow:focus {
opacity: .75;
}
.simpleLayerSearcher .esriLayerSearcherResults,
.invertedLayerSearcher .esriLayerSearcherResults {
display: none;
z-index: 99;
width: 100%;
position: absolute;
left: 0;
top: 100%;
margin: -1px 0 0;
border: 1px solid #57585A;
border-top: 0;
padding: 0;
background: #fff;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
.invertedLayerSearcher .esriLayerSearcherResults {
border: 1px solid #A8A7A5;
background: #333;
}
.simpleLayerSearcher .esriLayerSearcherResult,
.invertedLayerSearcher .esriLayerSearcherResult {
padding: 6px;
display: block;
cursor: pointer;
outline: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
.simpleLayerSearcher .esriLayerSearcherResultOdd,
.invertedLayerSearcher .esriLayerSearcherResultOdd {
}
.simpleLayerSearcher .esriLayerSearcherResult:hover,
.simpleLayerSearcher .esriLayerSearcherResultEven:focus,
.simpleLayerSearcher .esriLayerSearcherResultOdd:focus {
background-color: #ededed;
}
.invertedLayerSearcher .esriLayerSearcherResult:hover,
.invertedLayerSearcher .esriLayerSearcherResultEven:focus,
.invertedLayerSearcher .esriLayerSearcherResultOdd:focus {
background-color: #121212;
}
.simpleLayerSearcher .esriLayerSearcherResultLast,
.invertedLayerSearcher .esriLayerSearcherResultLast {
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
.simpleLayerSearcher .esriLayerSearcherResult .esriLayerSearcherResultPartial,
.invertedLayerSearcher .esriLayerSearcherResult .esriLayerSearcherResultPartial {
font-weight: 700;
}
.simpleLayerSearcher .esriLayerSearcherMenu,
.invertedLayerSearcher .esriLayerSearcherMenu {
display: none;
width: 100%;
z-index: 99;
position: absolute;
left: 0;
top: 100%;
margin: -1px 0 0;
padding: 0;
background: #fff;
border: 1px solid #57585A;
border-top: 0;
-webkit-border-radius: 0 0 5px 5px;
border-radius: 0 0 5px 5px;
}
.invertedLayerSearcher .esriLayerSearcherMenu {
background: #333;
border: 1px solid #A8A7A5;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcherMenu,
.dj_rtl .simpleLayerSearcher .esriLayerSearcherResults,
.dj_rtl .invertedLayerSearcher .esriLayerSearcherMenu,
.dj_rtl .invertedLayerSearcher .esriLayerSearcherResults {
left: auto;
right: 0;
}
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuHeader,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuHeader {
padding: 6px;
margin: 0;
display: block;
background: #666;
color: #fff;
font-weight: 700;
}
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuHeader {
background: #999;
color: #333;
}
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose {
display: block;
float: right;
width: 16px;
height: 16px;
background: url(simpleLayerSearcher.png) no-repeat -64px 0;
cursor: pointer;
outline: 0;
}
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose {
background: url(invertedLayerSearcher.png) no-repeat -48px 0;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose,
.dj_rtl .invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose {
float: left;
}
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose:hover,
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose:focus,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose:hover,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherMenuClose:focus {
opacity: .75;
}
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelectedCheck,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelectedCheck {
width: 16px;
height: 16px;
display: block;
float: right;
margin: 0 0 0 6px;
}
.dj_rtl .simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelectedCheck,
.dj_rtl .invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelectedCheck {
float: left;
margin: 0 6px 0 0;
}
.simpleLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelected .esriLayerSearcherSelectedCheck,
.invertedLayerSearcher .esriLayerSearcherMenu .esriLayerSearcherSelected .esriLayerSearcherSelectedCheck {
background: url(simpleLayerSearcher.png) no-repeat -16px 0;
}
input.query-error::-webkit-input-placeholder {
color: red;
}
input.query-error:-moz-placeholder { /* Firefox 18- */
color: red;
}
input.query-error::-moz-placeholder { /* Firefox 19+ */
color: red;
}
input.query-error:-ms-input-placeholder {
color: red;
}
<div class="${theme}" role="presentation">
<div class="${_css.LayerSearcherContainerClass}" role="presentation">
<div class="${_css.LayerSearcherClass}" data-dojo-attach-point="containerNode" role="presentation">
<div title="Search" tabindex="0" class="${_css.searchButtonClass} ${_css.LayerSearcherIconClass}" data-dojo-attach-point="submitNode" role="button"></div>
<div aria-haspopup="true" id="${id}_menu_button" title="Change Layer" tabindex="0" class="${_css.layerSearcherMenuArrowClass} ${_css.LayerSearcherIconClass}" data-dojo-attach-point="layerSearcherMenuArrowNode" role="button" aria-expanded="false"></div>
<input aria-haspopup="true" id="${id}_input" tabindex="0" placeholder="" value="${value}" autocomplete="off" type="text" data-dojo-attach-point="inputNode" role="textbox">
<div tabindex="0" class="${_css.clearButtonClass} ${_css.LayerSearcherIconClass}" data-dojo-attach-point="clearNode" role="button"></div>
<div class="${_css.LayerSearcherClearClass}" role="presentation"></div>
</div>
<div class="${_css.resultsContainerClass}" data-dojo-attach-point="resultsNode" aria-labelledby="${id}_input" role="menu" aria-hidden="true"></div>
<div class="${_css.layerSearcherMenuClass}" data-dojo-attach-point="layerSearcherMenuNode" role="presentation">
<div class="${_css.layerSearcherMenuHeaderClass}">
Select Layer
<div role="button" data-dojo-attach-point="layerSearcherMenuCloseNode" title="Close Menu" tabindex="0" class="${_css.layerSearcherMenuCloseClass}"></div>
<div class="${_css.LayerSearcherClearClass}" role="presentation"></div>
</div>
<div data-dojo-attach-point="layerSearcherMenuInsertNode" aria-labelledby="${id}_menu_button" role="menu" aria-hidden="true"></div>
</div>
</div>
</div>
/* global define, console, setTimeout, clearTimeout */
/* jshint undef: true */
/* jshint unused: true */
define([
"dojo/_base/declare",
"dojo/_base/lang",
"dojo/_base/Deferred",
"dojo/_base/event",
"dojo/_base/array",
"dojo/dom-attr",
"dojo/dom-class",
"dojo/dom-style",
"dojo/dom-construct",
"dojo/keys",
"dojo/on",
"dojo/query",
"dojo/text!./LayerSearch.html",
"dojo/uacss",
"dijit/a11yclick",
"dijit/_TemplatedMixin",
"dijit/focus",
"esri/SpatialReference",
"esri/graphic",
"esri/dijit/_EventedWidget",
"esri/geometry/Point",
"esri/geometry/Extent",
"esri/tasks/query",
"esri/tasks/QueryTask",
"esri/tasks/FindParameters",
"esri/tasks/FindTask"
],
function (
declare, lang, Deferred, event, arrayUtil, domAttr, domClass, domStyle, domConstruct, keys, on, query, template, has,
a11yclick, _TemplatedMixin, focusUtil,
SpatialReference, Graphic, _EventedWidget,
Point, Extent, esriQuery, QueryTask, FindParameters, FindTask) {
var Widget = declare("als.dijit.LayerSearch", [_EventedWidget, _TemplatedMixin], {
// Set template file HTML
templateString: template,
// On to Connect Event Mapping
_eventMap: {
"select": ["feature", "resultName"],
"find-results": ["results"],
"auto-complete": ["results"],
"layersearch-select": ["layerSearcher"],
"clear": true,
"enter-key-select": true,
"load": true
},
// init
constructor: function (options, srcRefNode) {
// class names
this._css = {
LayerSearcherContainerClass: 'esriLayerSearcherContainer',
LayerSearcherClass: 'esriLayerSearcher',
LayerSearcherMultipleClass: 'esriLayerSearcherMultiple',
LayerSearcherIconClass: 'esriLayerSearcherIcon',
LayerSearcherActiveClass: 'esriLayerSearcherActive',
LayerSearcherResultsOpenClass: 'esriLayerSearcherResultsOpen',
LayerSearcherMenuOpenClass: 'esriLayerSearcherMenuOpen',
loadingClass: 'esriLayerSearcherLoading',
resultsContainerClass: 'esriLayerSearcherResults',
resultsItemClass: 'esriLayerSearcherResult',
resultsItemEvenClass: 'esriLayerSearcherResultEven',
resultsItemOddClass: 'esriLayerSearcherResultOdd',
resultsItemFirstClass: 'esriLayerSearcherResultFirst',
resultsItemLastClass: 'esriLayerSearcherResultLast',
resultsPartialMatchClass: 'esriLayerSearcherResultPartial',
searchButtonClass: 'esriLayerSearcherSearch',
clearButtonClass: 'esriLayerSearcherReset',
hasValueClass: 'esriLayerSearcherHasValue',
layerSearcherMenuClass: 'esriLayerSearcherMenu',
layerSearcherMenuHeaderClass: 'esriLayerSearcherMenuHeader',
layerSearcherMenuCloseClass: 'esriLayerSearcherMenuClose',
activeMenuClass: 'esriLayerSearcherMenuActive',
layerSearcherMenuArrowClass: 'esriLayerSearcherMenuArrow',
layerSearcherSelectedClass: 'esriLayerSearcherSelected',
layerSearcherSelectedCheckClass: 'esriLayerSearcherSelectedCheck',
LayerSearcherClearClass: 'esriLayerSearcherClearFloat'
};
// default settings
this.options = {
autoComplete: false, // show autoComplete?
value: "", // Value of input
theme: "simpleLayerSearcher", // Theme
activeLayerSearcherIndex: 0,
maxLocations: 6, // Maximum result locations to return
minCharacters: 3, // Minimum amount of characters before searching
searchDelay: 300, // Delay before doing the query. To avoid being too chatty.
layerMenu: true,
autoNavigate: true, // Automatically navigate
showResults: true, // show result suggestions
map: null,
activeLayerSearcher: null,
zoomScale: 10000,
// new
layerSearchers: []
/*qLayerId: null,
qLayerLayers: [],
qFields: [],
qOIDField: 'OBJECTID',
qOutfields: [], // qFields and qOIDField automatically included
qLabelFunction: null // can only use fields that were in qFields, qOIDField, and qOutfields above
*/
};
// mix in settings and defaults
var defaults = lang.mixin({}, this.options, options);
// settings
this.set("autoComplete", defaults.autoComplete);
this.set("value", defaults.value);
this.set("theme", defaults.theme);
this.set("activeLayerSearcherIndex", defaults.activeLayerSearcherIndex);
this.set("maxLocations", defaults.maxLocations);
this.set("minCharacters", defaults.minCharacters);
this.set("searchDelay", defaults.searchDelay);
this.set("layerMenu", defaults.layerMenu);
this.set("autoNavigate", defaults.autoNavigate);
this.set("showResults", defaults.showResults);
this.set("map", defaults.map);
this.set("activeLayerSearcher", defaults.activeLayerSearcher);
this.set("layerSearchers", defaults.layerSearchers);
this.set("zoomScale", defaults.zoomScale);
// new
/*this.set("qLayerId", defaults.qLayerId);
this.set("qLayerLayers", defaults.qLayerLayers);
this.set("qFields", defaults.qFields);
this.set("qOIDField", defaults.qOIDField);
this.set("qOutfields", defaults.qOutfields);
this.set("qLabelFunction", defaults.qLabelFunction);*/
// results holder
this.set("results", []);
// deferreds
this._deferreds = [];
// default Spatial Ref
this._defaultSR = new SpatialReference(4326);
// watch updates of public properties and update the widget accordingly
this.watch("value", this._updateValue);
this.watch("theme", this._updateTheme);
this.watch("activeLayerSearcher", this._setActiveLayerSearcher);
this.watch("activeLayerSearcherIndex", this._setActiveLayerSearcherIndex);
this.watch("layerSearchers", this._updateLayerSearcher);
this.watch("layerMenu", this._updateLayerSearcher);
this.watch("map", this._setupEvents);
// widget node
this.domNode = srcRefNode;
},
/* ---------------- */
/* Public Functions */
/* ---------------- */
// start widget
startup: function () {
if (!this.layerSearchers.length) {
console.log('LayerSearch:: No layerSearchers defined.');
this.destroy();
return;
}
if (!this.domNode) {
console.log('LayerSearcher:: domNode is undefined.');
this.destroy();
return;
}
// if map is in options
if (this.get("map")) {
// once map is loaded
if (this.get("map").loaded) {
this._init();
} else {
on.once(this.get("map"), "load", lang.hitch(this, function () {
this._init();
}));
}
} else {
// lets go
this._init();
}
},
// post create widget function
postCreate: function () {
this.inherited(arguments);
// submit button
this.own(
on(this.submitNode, a11yclick, lang.hitch(this, this._findThenSelect))
);
// layerSearcher menu
this.own(
on(this.layerSearcherMenuArrowNode, a11yclick, lang.hitch(this, this._toggleLayerSearcherMenu))
);
// input click
this.own(
on(this.inputNode, a11yclick, lang.hitch(this, this._inputClick))
);
// clear text
this.own(
on(this.clearNode, a11yclick, lang.hitch(this, this.clear))
);
// hide menu
this.own(
on(this.layerSearcherMenuCloseNode, a11yclick, lang.hitch(this, this._hideLayerSearcherMenu))
);
// build layerSearcher list
this._updateLayerSearcher();
// setup connections
this._setupEvents();
// add clear button if already populated
if (this.get("value")) {
this._checkStatus();
}
// hide menus
this._hideMenus();
},
destroy: function () {
this._removeEvents();
// remove html
domConstruct.empty(this.domNode);
this.inherited(arguments);
},
// clear the input box
clear: function () {
// clear event
this.onClear();
// empty input value
domAttr.set(this.inputNode, 'value', '');
// set current text
this.set("value", '');
// empty results
this.set("results", []);
// get node of reset button and remove it's active class
domClass.remove(this.containerNode, this._css.hasValueClass);
domAttr.set(this.clearNode, 'title', '');
// remove active menus
this._hideMenus();
// hide loading
this._hideLoading();
// placeholder
domClass.remove(this.inputNode, 'query-error');
domAttr.set(this.inputNode, 'placeholder', this._placeholder);
this.focus();
},
// show widget
show: function () {
domStyle.set(this.domNode, 'display', 'block');
},
// hide widget
hide: function () {
domStyle.set(this.domNode, 'display', 'none');
},
// submit button selected
find: function (search) {
// set deferred variable
var def = new Deferred();
if (search) {
if (typeof search === 'string') {
// search string
this._findQuery(search).then(function (resp) {
def.resolve(resp);
});
} else {
def.reject('LayerSearcher:: Invalid find type');
}
} else {
// default use text string of input
this._findQuery(this.get('value')).then(function (resp) {
def.resolve(resp);
});
}
// give me my deferred
return def.promise;
},
// focus on input
focus: function () {
focusUtil.focus(this.inputNode);
},
// blur input
blur: function () {
// if current focus exists
if (focusUtil.curNode) {
// remove focus
focusUtil.curNode.blur();
}
// remove focus from input node
this.inputNode.blur();
// hide any menus
this._hideMenus();
},
// go to a location
select: function (e) {
// event
// hide menus
this._hideMenus();
// hide loading spinner
this._hideLoading();
// has extent and autoNavigate
if (e && e.hasOwnProperty('oid') && this.get("map")) {
var query = new esriQuery();
query.objectIds = [e.oid];
query.returnGeometry = true;
var selectQueryUrl = this.get("map").getLayer(this.get("activeLayerSearcher").qLayerId).url;
if (e.hasOwnProperty('layerNum')) {
selectQueryUrl += '/' + e.layerNum;
}
var selectQueryTask = new QueryTask(selectQueryUrl);
selectQueryTask.execute(query).then(lang.hitch(this, function (response) {
var selectedFeature;
if (response && response.features && (selectedFeature = response.features[0])) {
this.onSelect(selectedFeature, e.name);
if (this.get("autoNavigate")) {
this.centerOnFeature(selectedFeature);
}
}
}), lang.hitch(this, function (response) {
console.debug('error');
}));
}
},
centerOnFeature: function(f) {
var fGeometry = f.geometry;
if (fGeometry.type === 'point') {
this.get("map").centerAndZoom(fGeometry, 12);
} else {
this.get("map").setExtent(fGeometry.getExtent().expand(1.5), true);
}
},
/* ---------------- */
/* Public Events */
/* ---------------- */
// called after search has been selected
onSelect: function () {},
// called on results
onFindResults: function () {},
// called on results
onAutoComplete: function () {},
// when layerSearcher selected
onLayerSearcherSelect: function () {},
// when layerSearcher selected
onClear: function () {},
// on enter key
onEnterKeySelect: function () {},
// widget loaded
onLoad: function () {},
/* ---------------- */
/* Private Functions */
/* ---------------- */
_init: function () {
// set widget ready
this.set("loaded", true);
// loaded
this.onLoad();
},
_findQuery: function (search) {
var def = new Deferred();
// query and then Locate
this._query({
delay: 0,
search: search
}).then(lang.hitch(this, function (response) {
// emit event with response
this.onFindResults(response);
def.resolve(response);
}), lang.hitch(this, function (error) {
// emit result error
this.onFindResults(error);
def.reject(error);
}));
return def.promise;
},
// sets current locator object
_setActiveLayerSearcher: function () {
// set current active layerSearcher object
this.set("activeLayerSearcher", this._layerSearchers[this.get("activeLayerSearcherIndex")]);
var actLyrSrch = this.get("activeLayerSearcher");
// create query or find task
if (actLyrSrch.qLayerLayers && actLyrSrch.qLayerLayers.length) {
this._task = new FindTask(this.get("map").getLayer(actLyrSrch.qLayerId).url);
} else {
this._task = new QueryTask(this.get("map").getLayer(actLyrSrch.qLayerId).url);
}
// update placeholder nodes
this._updatePlaceholder();
},
// Combine and count all layerSearchers
_setLayerSearcherList: function () {
this._layerSearchers = this.get("layerSearchers");
},
// Update layerSearcher nodes
_updateLayerSearcher: function () {
this.set("activeLayerSearcherIndex", 0);
this._setLayerSearcherList();
this._setActiveLayerSearcher();
this._insertLayerSearcherMenuItems();
},
// Update placeholder nodes
_updatePlaceholder: function () {
// reset placehodler text to nothing
this._placeholder = '';
// if placeholder of active layerSearcher is set
if (this.get("activeLayerSearcher") && this.get("activeLayerSearcher").placeholder) {
// set placeholder to active layerSearcher placeholder
this._placeholder = this.get("activeLayerSearcher").placeholder;
}
// set placeholder onto nodes
domAttr.set(this.inputNode, 'placeholder', this._placeholder);
domAttr.set(this.submitNode, 'title', this._placeholder);
},
// update value of text box
_updateValue: function () {
var newVal = arguments[2];
// If we want to update value of input
if (!this._ignoreUpdateValue) {
domAttr.set(this.inputNode, 'value', newVal);
// check input box's status
this._checkStatus();
}
},
// update theme
_updateTheme: function () {
var oldVal = arguments[1];
var newVal = arguments[2];
domClass.remove(this.domNode, oldVal);
domClass.add(this.domNode, newVal);
},
// change active layerSearcher
_setActiveLayerSearcherIndex: function () {
var oldVal = arguments[1];
var newVal = arguments[2];
this.set("activeLayerSearcherIndex", newVal);
// set layerSearcher object
this._setActiveLayerSearcher();
this._hideMenus();
this._insertLayerSearcherMenuItems();
// event object
var evt = {
attr: this.get("activeLayerSearcher"),
oldVal: oldVal,
newVal: newVal
};
// emit event
this.onLayerSearcherSelect(evt);
},
// clear timeout for query
_clearQueryTimeout: function () {
// if timer exists
if (this._queryTimer) {
// remove timeout
clearTimeout(this._queryTimer);
}
},
// query for results and then execute a function
_query: function (e) {
// default query object
if (!e) {
// immediate, no delay
e = {
delay: 0
};
}
// default search query
if (!e.search) {
e.search = this.get("value");
}
// set deferred variable if needed to cancel it
var def = new Deferred();
def.then(function() {
// silent callback.
}, function() {
// less silent errback.
console.warn('def errback');
console.warn(arguments);
return [];
});
this._deferreds.push(def);
// if we have a delay
if(e.delay){
// clear timeout for query
this._clearQueryTimeout();
// timeout
this._queryTimer = setTimeout(lang.hitch(this, function () {
// start the task
this._performTask(def, e);
// set timer to null
this._queryTimer = null;
}), e.delay);
}
else{
// start the task
this._performTask(def, e);
}
return def.promise;
},
// when layerSearcher search starts
_performTask: function (def, e) {
var actLyrSrch = this.get("activeLayerSearcher");
// if query isn't empty
if (e.search) {
// hide menu to toggle layerSearcher
this._hideLayerSearcherMenu();
// show loading spinner
this._showLoading();
// query or find parameters
var params, taskType;
if (actLyrSrch.qLayerLayers && actLyrSrch.qLayerLayers.length) {
params = new FindParameters();
params.layerIds = actLyrSrch.qLayerLayers;
params.searchFields = actLyrSrch.qFields;
params.searchText = e.search;
taskType = 'find';
} else {
params = new esriQuery();
params.outFields = actLyrSrch.qFields.concat(actLyrSrch.qOutfields);
params.outFields.push(actLyrSrch.qOIDField);
var whereArr = actLyrSrch.qFields.map(function(qField) {
return "UPPER(" + qField + ") LIKE UPPER('%" + e.search + "%')";
});
params.where = whereArr.join(" OR ");
taskType = 'query';
}
params.returnGeometry = false;
// within extent
/*if (this.get("activeLayerSearcher").searchExtent) {
params.geometry = this.get("activeLayerSearcher").searchExtent;
}*/
this._task.execute(params).then(lang.hitch(this, function (response) {
this._receivedResults(response, def, taskType, e);
}), lang.hitch(this, function (response) {
this._receivedResults(response, def, taskType, e);
}));
} else {
this._hideLoading();
def.reject('LayerSearcher:: no search to perform');
}
},
// called on AC Results
_showResults: function () {
// hide menu to toggle layerSearcher
this._hideLayerSearcherMenu();
// string to set
var html = '';
// if results and result node
if (this.get("results") && this.get("results").length && this.resultsNode) {
// textbox value
var partialMatch = this.get("value"),
i;
// partial match highlight
var r = new RegExp('(' + partialMatch + ')', 'gi');
html += '<ul role="presentation">';
// for each result
for (i = 0; i < this.get("results").length && i < this.get("maxLocations"); ++i) {
// location text
var text = this.get("results")[i].text || this.get("results")[i].name;
// set layer class
var layerClass = this._css.resultsItemClass + ' ';
// if it's odd
if (i % 2 === 0) {
// set it to odd
layerClass += this._css.resultsItemOddClass;
} else {
// even
layerClass += this._css.resultsItemEvenClass;
}
if (i === 0) {
// first item
layerClass += ' ' + this._css.resultsItemFirstClass;
} else if (i === (this.get("results").length - 1)) {
// last item
layerClass += ' ' + this._css.resultsItemLastClass;
}
// create list item
html += '<li title="' + text + '" data-text="' + text + '" data-item="true" data-index="' + i + '" role="menuitem" tabindex="0" class="' + layerClass + '">' + text.replace(r, '<strong class="' + this._css.resultsPartialMatchClass + '">$1</strong>') + '</li>';
}
// close list
html += '</ul>';
// insert HTML
if (this.resultsNode) {
this.resultsNode.innerHTML = html;
}
this._autoCompleteEvent();
// show!
this._showResultsMenu();
} else {
// set to blank HTML string
if (this.resultsNode) {
this.resultsNode.innerHTML = html;
}
// hide menu
this._hideResultsMenu();
}
},
// ac query
_autoComplete: function () {
// query with delay set
this._query({
delay: this.get("searchDelay"),
autoComplete: true,
search: this.get("value")
}).then(lang.hitch(this, function (response) {
// emit autocomplete event
this.onAutoComplete(response);
if (this.get("showResults")) {
// show results if allowed
this._showResults(response);
}
}));
},
// received results
_receivedResults: function (response, def, taskType) {
// hide loading spinner
this._hideLoading();
// convert results to desired format
var results = this._hydrateResults(response, taskType);
// save results
this.set("results", results);
// results object
var obj = {
"results": results,
"value": this.get("value")
};
def.resolve(obj);
},
// show loading spinner
_showLoading: function () {
domClass.add(this.containerNode, this._css.loadingClass);
},
// hide loading spinner
_hideLoading: function () {
domClass.remove(this.containerNode, this._css.loadingClass);
},
// show layerSearcher selection menu
_showLayerSearcherMenu: function () {
// add class to container
domClass.add(this.containerNode, this._css.activeMenuClass);
domClass.add(this.domNode, this._css.LayerSearcherMenuOpenClass);
// display menu node
domStyle.set(this.layerSearcherMenuNode, 'display', 'block');
// aria
domAttr.set(this.layerSearcherMenuInsertNode, 'aria-hidden', 'false');
domAttr.set(this.layerSearcherMenuArrowNode, 'aria-expanded', 'true');
},
// hide layerSearcher selection menu
_hideLayerSearcherMenu: function () {
domClass.remove(this.containerNode, this._css.activeMenuClass);
domClass.remove(this.domNode, this._css.LayerSearcherMenuOpenClass);
domStyle.set(this.layerSearcherMenuNode, 'display', 'none');
// aria
domAttr.set(this.layerSearcherMenuInsertNode, 'aria-hidden', 'true');
domAttr.set(this.layerSearcherMenuArrowNode, 'aria-expanded', 'false');
},
// toggle layerSearcher selection menu
_toggleLayerSearcherMenu: function () {
// hide results
this._hideResultsMenu();
var display = domStyle.get(this.layerSearcherMenuNode, 'display');
// if layerSearcher menu is displayed
if (display === 'block') {
this._hideLayerSearcherMenu();
} else {
this._showLayerSearcherMenu();
}
},
// show autolocate menu
_showResultsMenu: function () {
// add class to container
domClass.add(this.containerNode, this._css.LayerSearcherActiveClass);
domClass.add(this.domNode, this._css.LayerSearcherResultsOpenClass);
// show node
domStyle.set(this.resultsNode, 'display', 'block');
// aria
domAttr.set(this.resultsNode, 'aria-hidden', 'false');
},
// hide the results menu
_hideResultsMenu: function () {
// hide
domStyle.set(this.resultsNode, 'display', 'none');
// add class to container
domClass.remove(this.containerNode, this._css.LayerSearcherActiveClass);
domClass.remove(this.domNode, this._css.LayerSearcherResultsOpenClass);
// aria
domAttr.set(this.resultsNode, 'aria-hidden', 'true');
},
// hide both menus
_hideMenus: function () {
this._hideLayerSearcherMenu();
this._hideResultsMenu();
},
// create menu for changing active layerSearcher
_insertLayerSearcherMenuItems: function () {
if (this.get("layerMenu") && this._layerSearchers && this._layerSearchers.length > 1) {
var html = '';
var layerClass = '',
i;
html += '<ul role="presentation">';
for (i = 0; i < this._layerSearchers.length; i++) {
// set layer class
layerClass = this._css.resultsItemClass + ' ';
// if it's odd
if (i % 2 === 0) {
// set it to odd
layerClass += this._css.resultsItemOddClass;
} else {
// even
layerClass += this._css.resultsItemEvenClass;
}
if (i === this.get("activeLayerSearcherIndex")) {
// currently selected layerSearcher
layerClass += ' ' + this._css.layerSearcherSelectedClass;
}
if (i === 0) {
// first in list
layerClass += ' ' + this._css.resultsItemFirstClass;
} else if (i === (this._layerSearchers.length - 1)) {
// last in list
layerClass += ' ' + this._css.resultsItemLastClass;
}
// layerSearcher name
var layerSearcherName = this._layerSearchers[i].name || 'Layer Search';
// create list item
html += '<li data-index="' + i + '" data-item="true" role="menuitem" tabindex="0" class="' + layerClass + '">';
html += '<div class="' + this._css.layerSearcherSelectedCheckClass + '"></div>';
html += layerSearcherName;
html += '<div class="' + this._css.LayerSearcherClearClass + '"></div>';
html += '</li>';
}
// close list
html += '</ul>';
this.layerSearcherMenuInsertNode.innerHTML = html;
// create menu event
this._layerSearcherMenuEvent();
// set display for nodes
domStyle.set(this.layerSearcherMenuNode, 'display', 'none');
domStyle.set(this.layerSearcherMenuArrowNode, 'display', 'block');
// add class
domClass.add(this.containerNode, this._css.LayerSearcherMultipleClass);
} else {
// remove html
this.layerSearcherMenuInsertNode.innerHTML = '';
// set display for nodes
domStyle.set(this.layerSearcherMenuNode, 'display', 'none');
domStyle.set(this.layerSearcherMenuArrowNode, 'display', 'none');
// add class
domClass.remove(this.containerNode, this._css.LayerSearcherMultipleClass);
}
},
// check input box's status
_checkStatus: function () {
// if input value is not empty
if (this.get("value")) {
// add class to dom
domClass.add(this.containerNode, this._css.hasValueClass);
// set class and title
domAttr.set(this.clearNode, 'title', 'Clear Search');
} else {
// clear address
this.clear();
}
},
_autoCompleteEvent: function () {
// list items
var lists = query('[data-item="true"]', this.resultsNode);
// remove event
if (this._acEvent) {
this._acEvent.remove();
}
// list item click
this._acEvent = on(lists, 'click, keydown', lang.hitch(this, function (e) {
// clear timeout for query
this._clearQueryTimeout();
// index of list item
var resultIndex = parseInt(domAttr.get(e.currentTarget, 'data-index'), 10);
// input box text
var locTxt = domAttr.get(e.currentTarget, 'data-text');
// next/previous index
var newIndex;
if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === keys.ENTER)) {
// set input text value to text
domAttr.set(this.inputNode, 'value', locTxt);
// set current text var
this.set("value", locTxt);
// we have results and index
if (this.get("results") && this.get("results")[resultIndex]) {
// result
var result = this.get("results")[resultIndex];
// if result has name
if (result.name) {
// select result
this.select(result);
} else {
// its a a suggest result
var text = result.text;
// new immediate query for result
var params = {
delay: 0,
search: text
};
// perform query
this._query(params).then(lang.hitch(this, function (response) {
// select location
this.select(response.results[0]);
}));
}
}
}
else if (e.type === 'keydown' && (e.keyCode === keys.BACKSPACE || e.keyCode === keys.DELETE)) {
event.stop(e);
this.inputNode.focus();
// backspace from current value
var newVal = this.inputNode.value.slice(0,-1);
domAttr.set(this.inputNode, 'value', newVal);
this.set("value", newVal);
}
else if (e.type === 'keydown' && e.keyCode === keys.UP_ARROW) {
event.stop(e);
// go to previous item
newIndex = resultIndex - 1;
// if first item
if (newIndex < 0) {
// go back to input
this.inputNode.focus();
} else {
// go to previous item
lists[newIndex].focus();
}
} else if (e.type === 'keydown' && e.keyCode === keys.DOWN_ARROW) {
event.stop(e);
// go to next item
newIndex = resultIndex + 1;
// if last item
if (newIndex >= lists.length) {
// go to input node
this.inputNode.focus();
} else {
// go to next item
lists[newIndex].focus();
}
} else if (e.keyCode === keys.ESCAPE) { // esc key
// hide menus
this._hideMenus();
}
}));
},
_layerSearcherMenuEvent: function () {
// list items
var lists = query('[data-item="true"]', this.layerSearcherMenuInsertNode);
// remove event
if (this._gmEvent) {
this._gmEvent.remove();
}
// select layerSearcher item
this._gmEvent = on(lists, 'click, keydown', lang.hitch(this, function (e) {
// index of list item
var resultIndex = parseInt(domAttr.get(e.currentTarget, 'data-index'), 10);
// next/previous index
var newIndex;
if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === keys.ENTER)) {
// change to layerSearcher
this._setActiveLayerSearcherIndex(null, null, resultIndex);
this._hideLayerSearcherMenu();
} else if (e.type === 'keydown' && e.keyCode === keys.UP_ARROW) {
event.stop(e);
// go to previous item
newIndex = resultIndex - 1;
if (newIndex < 0) {
this.layerSearcherMenuArrowNode.focus();
} else {
lists[newIndex].focus();
}
} else if (e.type === 'keydown' && e.keyCode === keys.DOWN_ARROW) {
event.stop(e);
// go to next item
newIndex = resultIndex + 1;
if (newIndex >= lists.length) {
this.layerSearcherMenuArrowNode.focus();
} else {
lists[newIndex].focus();
}
} else if (e.keyCode === keys.ESCAPE) { // esc key
this._hideLayerSearcherMenu();
}
}));
},
_removeEvents: function () {
var i;
// if delegations
if (this._events && this._events.length) {
// disconnect all events
for (i = 0; i < this._events.length; i++) {
this._events[i].remove();
}
}
if (this._acEvent) {
this._acEvent.remove();
}
if (this._gmEvent) {
this._gmEvent.remove();
}
// array of all connections
this._events = [];
},
// set up connections
_setupEvents: function () {
this._removeEvents();
// close on click
var closeOnClick = on(document, "click", lang.hitch(this, function (e) {
this._hideResultsMenu(e);
}));
this._events.push(closeOnClick);
// input key up
var inputKeyUp = on(this.inputNode, "keyup", lang.hitch(this, function (e) {
this._inputKeyUp(e);
}));
this._events.push(inputKeyUp);
// input key down
var inputKeyDown = on(this.inputNode, "keydown", lang.hitch(this, function (e) {
this._inputKeyDown(e);
}));
this._events.push(inputKeyDown);
// arrow key down
var layerSearcherMenuButtonKeyDown = on(this.layerSearcherMenuArrowNode, "keydown", this._layerSearcherMenuButtonKeyDown());
this._events.push(layerSearcherMenuButtonKeyDown);
// if map set
if (this.get("map")) {
var mapClick = on(this.get("map"), "click", lang.hitch(this, function () {
this.blur();
}));
this._events.push(mapClick);
}
this._layerSearcherMenuEvent();
this._autoCompleteEvent();
},
// find then immediately select first result
_findThenSelect: function () {
this.find().then(lang.hitch(this, function (response) {
// if we have a result
if (response.results && response.results.length) {
// select result
var topResult = response.results[0];
this.select(topResult);
domAttr.set(this.inputNode, 'value', topResult.name);
// set current text var
this.set("value", topResult.name);
// emit event
this.onEnterKeySelect();
} else {
domClass.add(this.inputNode, 'query-error');
domAttr.set(this.inputNode, 'value', '');
this._ignoreUpdateValue = true;
this.set("value", '');
this._ignoreUpdateValue = false;
domAttr.set(this.inputNode, 'placeholder', 'No results');
}
}));
},
// key up event on input box
_inputKeyUp: function (e) {
if (e) {
// Reset timer between keys
this._clearQueryTimeout();
// get textbox value
var aquery = this.inputNode.value;
// don't update input
this._ignoreUpdateValue = true;
// update current text variable
this.set("value", aquery);
// update input
this._ignoreUpdateValue = false;
// length of value
var alength = 0;
// if value
if (aquery) {
// set length of value
alength = aquery.length;
}
// ignored keys
if (e.ctrlKey || e.shiftKey || e.metaKey || e.altKey || e.keyCode === keys.copyKey || e.keyCode === keys.ALT || e.keyCode === keys.CTRL || e.keyCode === keys.META || e.keyCode === keys.SHIFT || e.keyCode === keys.UP_ARROW || e.keyCode === keys.DOWN_ARROW || e.keyCode === keys.LEFT_ARROW || e.keyCode === keys.RIGHT_ARROW) {
return e;
} else if (e && e.keyCode === keys.ENTER) { // if enter key was pushed
this._cancelDeferreds();
// query then Locate
this._findThenSelect();
// if up arrow pushed
} else if (e && e.keyCode === keys.ESCAPE) { // esc key
this._cancelDeferreds();
this._hideMenus();
} else if (e && e.keyCode === keys.TAB) {
this._cancelDeferreds();
this._hideMenus();
} else if (this.get("autoComplete") && alength >= this.get("minCharacters")) {
this._autoComplete();
} else {
// hide menus
this._hideMenus();
}
// check status of search box
this._checkStatus();
}
},
// stop existing queries
_cancelDeferreds: function () {
if (this._deferreds.length) {
arrayUtil.forEach(this._deferreds, function(def) {
if (!def.isFulfilled() && !def.isResolved()) {
//TODO: def.preventdefault? event.stop?
// canceled requests just throw a bunch of errors. it's very sad.
def.cancel('FeatureFinder:: stop query', true);
}
});
this._deferreds = [];
}
},
// key down event on input box
_inputKeyDown: function (e) {
var lists = query('[data-item="true"]', this.resultsNode);
if (e && e.keyCode === keys.TAB) {
this._cancelDeferreds();
// hide menus if opened
this._hideMenus();
// stop
return;
} else if (e && e.keyCode === keys.UP_ARROW) {
event.stop(e);
// get list item length
var listsLen = lists.length;
// if not zero
if (listsLen) {
// go to previous list item
lists[listsLen - 1].focus();
}
} else if (e && e.keyCode === keys.DOWN_ARROW) {
event.stop(e);
// if first item
if (lists[0]) {
// focus first item
lists[0].focus();
}
}
},
// layerSearcher menu arrow key down
_layerSearcherMenuButtonKeyDown: function (e) {
var lists = query('[data-item="true"]', this.layerSearcherMenuInsertNode);
if (e && e.keyCode === keys.UP_ARROW) {
event.stop(e);
this._showLayerSearcherMenu();
// get list item length
var listsLen = lists.length;
// if not zero
if (listsLen) {
// go to previous list item
lists[listsLen - 1].focus();
}
} else if (e && e.keyCode === keys.DOWN_ARROW) {
event.stop(e);
this._showLayerSearcherMenu();
// if first item
if (lists[0]) {
// focus first item
lists[0].focus();
}
}
},
// input box clicked
_inputClick: function () {
// hide layerSearcher switch
this._hideLayerSearcherMenu();
// if input value is empty
if (!this.get("value")) {
// clear address
this.clear();
// hide menus
this._hideMenus();
}
// check status of text box
this._checkStatus();
},
_hydrateResult: function (e, actLyrSrch, actLyrSrchIdx, taskType) {
// result to add
var featAttributes = (taskType === 'find') ? e.feature.attributes : e.attributes;
var oidField = (Array.isArray(actLyrSrch.qOIDField)) ? actLyrSrch.qOIDField[actLyrSrchIdx] : actLyrSrch.qOIDField;
var newResult = {
name: actLyrSrch.qLabelFunction(featAttributes, e.layerId),
oid: featAttributes[oidField],
};
if (taskType === 'find') {
newResult.layerNum = e.layerId;
}
return newResult;
},
// create Extent and Graphic objects from JSON
_hydrateResults: function (e, taskType) {
// return results array
var results = [],
i = 0,
newResult,
actLyrSrch = this.get("activeLayerSearcher"),
actLyrSrchIdx = this.get("activeLayerSearcherIndex");
// if results
var featArr = (taskType === 'find') ? e : e.features;
if (featArr && featArr.length) {
for (i; i < featArr.length && i < this.get("maxLocations"); i++) {
newResult = this._hydrateResult(featArr[i], actLyrSrch, actLyrSrchIdx, taskType);
// add to return array
results.push(newResult);
}
}
return results;
}
});
return Widget;
});
debugger;
var map, layersearch;
require([
"esri/map",
"application/LayerSearch",
"dojo/on",
"esri/symbols/SimpleMarkerSymbol",
"dojo/_base/Color",
"esri/InfoTemplate",
"esri/graphic",
"esri/geometry/Extent",
"esri/geometry/Multipoint",
"esri/geometry/Polygon",
"esri/geometry/Polyline",
"esri/geometry/ScreenPoint",
"esri/layers/FeatureLayer",
"esri/layers/ArcGISDynamicMapServiceLayer",
"dojo/domReady!"
], function (
Map,
LayerSearch,
on,
SimpleMarkerSymbol,
Color,
InfoTemplate,
Graphic,
Extent,
Multipoint,
Polygon,
Polyline,
ScreenPoint,
FeatureLayer,
AGSDMSLayer
) {
debugger;
map = new Map("map", {
basemap: "gray",
center: [-120.435, 46.159], // lon, lat
zoom: 7
});
map.addLayer(new AGSDMSLayer('http://tmservices1.esri.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer/', {
id: 'fireLayer'
}));
var windLayer = map.addLayer(new FeatureLayer('http://tmservices1.esri.com/arcgis/rest/services/LiveFeeds/NOAA_METAR_current_wind_speed_direction/MapServer/0/', {
outFields: ['WIND_DIRECT'],
id: 'windLayer'
}));
windLayer.on('load', function(response) {
response.layer.renderer.setRotationInfo({
field: 'WIND_DIRECT',
type: 'geographic'
});
});
var layerSearchers = setupLayerSearchers();
var lsOptions = setupLSOptions(layerSearchers);
layersearch = new LayerSearch(lsOptions, "search");
layersearch.startup();
// on search results
on(layersearch, 'find-results', function (results) {
console.log('search', results);
});
// on search results
on(layersearch, 'featuresearch-select', function (results) {
console.log('onselect', results);
});
// on search results
on(layersearch, 'auto-complete', function (results) {
console.log('autocomplete', results);
});
// on params
on(layersearch, 'select', function (results) {
console.log('params', results);
});
layersearch.on('load', function () {
});
function setupLayerSearchers() {
return [{
name: 'Search fire names in wildfire activity', // name for dropdown menu
placeholder: 'Find fires',
qLayerId: 'fireLayer',
//qFields: ['FIRE_NAME', 'Fire name'], // field(s) to search on. if blank on a dynamic map service, it searches all fields. if blank on a feature layer, it doesn't search.
qLayerLayers: [0, 1, 3], // sublayer(s) to search on for dynamic map service. do not include this param for a feature layer
qOIDField: ['OBJECTID', 'OBJECTID', 'OBJECTID'], // need objectId field for each of the layers.
//qOutfields: ['STATE'], // any fields needed below besides qFields and qOIDField. only necessary for featureLayer. ignored for dynamicMapService.
qLabelFunction: function(feat, lyrNum) { // returns string for autocomplete dropdown. in a featureLayer, all fields used below must be in the qFields, qOIDField, and qOutfields options above -- otherwise those attributes won't be returned with the feature query.
switch (lyrNum) {
case 0:
case 1:
return (feat.FIRE_NAME || feat['Fire name']) + ' fire in ' + (feat.STATE || feat.State);
case 3:
return feat.FIRE_NAME + ' fire perimeter, ' + feat.UNIT_ID;
default:
return 'Unknown fire name';
}
}
}, {
name: 'Search station names in windspeed reports',
placeholder: 'Find stations',
qLayerId: 'windLayer',
qFields: ['STATION_NAME'],
qOIDField: 'OBJECTID',
qOutfields: ['COUNTRY'],
qLabelFunction: function(feat) { return feat.STATION_NAME + ' station in ' + feat.COUNTRY; }
}];
}
function setupLSOptions(lsArr) {
return {
map: map,
minCharacters: 3,
autoNavigate: true,
autoComplete: true,
theme: "invertedLayerSearcher",
layerSearchers: lsArr
};
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment