Common_CustomTasks_CSharp\QueryBuilderTask_CSharp\QueryBuilderTask.js
Copyright 2010 ESRI All rights reserved under the copyright laws of the United States and applicable international laws, treaties, and conventions. You may freely redistribute and use this sample code, with or without modification, provided you include the original copyright notice and use restrictions. See <a href="http://help.arcgis.com/en/sdk/10.0/usageRestrictions.htm">the use restrictions</a>. //==================================================== // QueryBuilderTask client-side control definition //==================================================== Type.registerNamespace('ESRI.ADF.Samples.CustomTasks'); // Constructor for the QueryBuilderTask, used here to declare member variables. Since the task // is implemented as an AJAX component, initialization logic is placed in the initialize function. ESRI.ADF.Samples.CustomTasks.QueryBuilderTask = function(element) { ESRI.ADF.Samples.CustomTasks.QueryBuilderTask.initializeBase(this, [element]); this._id = null; this._layer = null; this._field = null; this._operator = null; this._sampleValue = null; this._fieldCache = null; this._valueCache = null; } ESRI.ADF.Samples.CustomTasks.QueryBuilderTask.prototype = { //=================== // Initialization //=================== initialize: function() { // initialize instance members that reference child server controls on the task this._layerDropDownList = this.get__layerDropDownList(); this._retrievingLayersDiv = this.get__retrievingLayersDiv(); this._fieldDropDownList = this.get__fieldDropDownList(); this._retrievingFieldsDiv = this.get__retrievingFieldsDiv(); this._operatorDropDownList = this.get__operatorDropDownList(); this._sampleValueDropDownList = this.get__sampleValueDropDownList(); this._retrievingSampleValuesDiv = this.get__retrievingSampleValuesDiv(); this._addToQueryButton = this.get__addToQueryButton(); this._andButton = this.get__andButton(); this._orButton = this.get__orButton(); this._notButton = this.get__notButton(); this._leftParenthesisButton = this.get__leftParenthesisButton(); this._rightParenthesisButton = this.get__rightParenthesisButton(); this._percentButton = this.get__percentButton(); this._underscoreButton = this.get__underscoreButton(); this._queryTextBox = this.get__queryTextBox(); this._clearQueryButton = this.get__clearQueryButton(); this._doQueryButton = this.get__doQueryButton(); this._executingQueryDiv = this.get__executingQueryDiv(); // Specify the task's DOM element (UI) event handlers $addHandler(this._layerDropDownList, 'change', this._onLayerChanged); $addHandler(this._fieldDropDownList, 'change', this._onFieldChanged); $addHandler(this._addToQueryButton, 'click', this._addToQueryButtonClick); $addHandler(this._andButton, 'click', this._operatorButtonClick); $addHandler(this._orButton, 'click', this._operatorButtonClick); $addHandler(this._notButton, 'click', this._operatorButtonClick); $addHandler(this._underscoreButton, 'click', this._operatorButtonClick); $addHandler(this._percentButton, 'click', this._operatorButtonClick); $addHandler(this._leftParenthesisButton, 'click', this._operatorButtonClick); $addHandler(this._rightParenthesisButton, 'click', this._operatorButtonClick); $addHandler(this._operatorDropDownList, 'change', this._onOperatorChanged); $addHandler(this._sampleValueDropDownList, 'change', this._onSampleValueChanged); $addHandler(this._clearQueryButton, 'click', this._clearQueryButtonClick); $addHandler(this._doQueryButton, 'click', this._doQueryButtonClick); // Specify handlers for server-side method completion events this.add_onGetJsonLayersComplete(this._updateLayerDropDown); this.add_onGetJsonFieldsComplete(this._updateFieldDropDown); this.add_onGetSampleValuesComplete(this._updateValueDropDown); this.add_onDoQueryComplete(this._hideQueryingIndicators); // Call method to retrieve layers for the current map and resource. Logic to // retrieve fields, operators, and sample values will also fire as a result of // the layer being changed. this.getLayers(); // Initialize the base class ESRI.ADF.Samples.CustomTasks.QueryBuilderTask.callBaseMethod(this, 'initialize'); }, // Array mapping field types to field category (e.g. Text or Numeric) FieldTypes: { 'String': 'Text', 'Char': 'Text', 'Byte': 'Numeric', 'Double': 'Numeric', 'Float': 'Numeric', 'Long': 'Numeric', 'Int': 'Numeric', 'Int16': 'Numeric', 'Int32': 'Numeric', 'Int64': 'Numeric', 'Short': 'Numeric', 'Single': 'Numeric' }, // Arrays specifying numeric and text operators NumericOperators: ['=', '<', '>', '<=', '>=', '<>'], TextOperators: ['=', '<>', 'LIKE', 'IS', 'IS NOT'], //============================ // Public property accessors //============================ // Returns the object's id get_id: function() { return this._id; }, // Sets the object's id set_id: function(value) { this._id = value; }, // Returns the currently selected layer get_layer: function() { return this._layer; }, // Sets the currently selected layer set_layer: function(value) { this._layer = value; }, // Returns the currently selected field get_field: function() { return this._field; }, // Sets the currently selected field set_field: function(value) { this._field = value; }, // Returns the currently selected operator get_operator: function() { return this._operator; }, // Sets the currently selected operator set_operator: function(value) { this._operator = value; }, // Returns the currently selected sample value get_sampleValue: function() { return this._value; }, // Sets the currently selected sample value set_sampleValue: function(value) { this._value = value; }, //============================ // Public functions //============================ // Retrieves layers for the current map and resource(s) from the server getLayers: function() { // Hide the layers drop-down list and show the layer retrieval activity indicator this._retrievingLayersDiv.style.display = 'inline'; this._layerDropDownList.style.display = 'none'; // Call the server-side task method to retrieve the layers as JSON. As specified in // the initialize method, the _updateLayerDropDown function handles the result that // is returned to the client. this.GetJsonLayers(); }, // Adds the specified JSON field array to the task's field cache. The other parameters // are used as a key. addFieldsToCache: function(layerName, mapResourceName, mapID, jsonFieldArray) { // Instantiate the cache if it has not been if (!this._fieldCache) this._fieldCache = new Object(); // Format the passed-in layer name, map resource name, and map ID into a key var fieldIndex = String.format('{0}:{1}:{2}', layerName, mapResourceName, mapID); // Store the passed-in JSON string as a property on the fieldCache object this._fieldCache[fieldIndex] = jsonFieldArray; }, // Attempts to retrieve the JSON field array specified by the passed-in parameters getFieldsFromCache: function(layerName, mapResourceName, mapID) { // Make sure the task's field cache has been instantiated if (!this._fieldCache) return null; // Return the property on the field cache object having a key that matches the passed-in // parameters var fieldIndex = String.format('{0}:{1}:{2}', layerName, mapResourceName, mapID); return this._fieldCache[fieldIndex]; }, // Adds the specified JSON value array to the task's value cache. The other parameters // are used as a key. addValuesToCache: function(fieldName, layerName, mapResourceName, mapID, jsonValueArray) { // Instantiate the cache if it has not been if (!this._valueCache) this._valueCache = new Object(); // Format the passed-in field name, layer name, map resource name, and map ID into a key var valueIndex = String.format('{0}:{1}:{2}:{3}', fieldName, layerName, mapResourceName, mapID); // Store the passed-in JSON string as a property on the valueCache object this._valueCache[valueIndex] = jsonValueArray; }, // Attempts to retrieve the JSON value array specified by the passed-in parameters getValuesFromCache: function(fieldName, layerName, mapResourceName, mapID) { // Make sure the task's value cache has been instantiated if (!this._valueCache) return null; // Return the property on the value cache object having a key that matches the passed-in // parameters var valueIndex = String.format('{0}:{1}:{2}:{3}', fieldName, layerName, mapResourceName, mapID); return this._valueCache[valueIndex]; }, //============================= // DOM Element Event Handlers //============================= // Fires when the value of the layer drop-down list changes _onLayerChanged: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Clear the query textbox task._clearQuery(); // Get the index of the currently selected item in the layer drop-down var layerIndex = task._layerDropDownList.selectedIndex; // Get the JSON encapsulating the currently selected layer's properties. This is stored // as the value of items in the layer drop-down. var jsonLayer = task._layerDropDownList.options[layerIndex].value; // Use the AJAX JavaScriptSerializer to convert the JSON into an object var layer = Sys.Serialization.JavaScriptSerializer.deserialize(jsonLayer); // Update the task's layer property task.set_layer(layer); // Attempt to retrieve the layer's field information from the task's cache var cachedFields = task.getFieldsFromCache(layer.name, layer.mapResource, layer.map); // Check whether fields have already been cached for the layer if (cachedFields) { // Since fields have already been cached, fire the method that otherwise executes // after fields are retrieved from the server. We do this because we are essentially // substituting field retrieval from the server with retrieval from client-side cache. task._onGetJsonFieldsComplete(cachedFields); } else { // Fields for this layer have not been cached, so they need to be retrieved from // the server // Show the field retrieval activity indicator task._retrievingFieldsDiv.style.display = 'inline'; task._fieldDropDownList.style.display = 'none'; // Call the server-side method that retrieves field information. As specified in // the client-side initialize function, the _updateFieldDropDown function processes // the fields that are returned to the client. task.GetJsonFields(jsonLayer); } }, // Fires when the value of the fields drop-down list changes _onFieldChanged: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Get the index of the item currently selected in the field drop-down var fieldIndex = task._fieldDropDownList.selectedIndex; // Get the JSON representation of the currently selected field. This is stored as the // value of the drop-down item. var jsonField = task._fieldDropDownList.options[fieldIndex].value; // Use the AJAX JavaScriptSerializer to convert the JSON to a JavaScript object var field = Sys.Serialization.JavaScriptSerializer.deserialize(jsonField); // Update the task's field property task.set_field(field); // Call method to update the list of operators (i.e. change to text or numeric) based on // the current field task._updateOperatorList(); // Retrieve the currently selected layer var layer = task.get_layer(); // Attempt to retrieve sample values for the field from the task's cache var cachedValues = task.getValuesFromCache(field.name, layer.name, layer.mapResource, layer.map); // Check whether values were found in the cache if (cachedValues) { // Since sample values have already been cached, fire the method that otherwise // executes after values are retrieved from the server. We do this because we are // essentially substituting sample value retrieval from the server with retrieval from // client-side cache. task._onGetSampleValuesComplete(cachedValues); } else { // Sample values for this field have not been cached, so they need to be retrieved // from the server // Show the sample value retrieval activity indicator task._retrievingSampleValuesDiv.style.display = 'inline'; task._sampleValueDropDownList.style.display = 'none'; // Call the server-side method that retrieves sample values. As specified in the task's // client-side initialize function, the _updateValueDropDown function processes the // values that are returned to the client. task.GetSampleValues(field.name, layer.id, layer.mapResource); } }, // Fires when the value of the operator drop-down list changes _onOperatorChanged: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Update the task's operator property with the newly selected value var index = task._operatorDropDownList.selectedIndex; task.set_operator(task._operatorDropDownList.options[index].text); }, // Fires when the value of the sample value drop-down list changes _onSampleValueChanged: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Update the task's sampleValue property with the newly selected value var index = task._sampleValueDropDownList.selectedIndex; task.set_sampleValue(task._sampleValueDropDownList.options[index].text); }, // Fires when one of the operator buttons (e.g. And, Or, %, etc.) is clicked _operatorButtonClick: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Add the text of the button to the end of the query task._queryTextBox.value += this.value; // Move the cursor in the query textbox to the end task._moveQueryCursorToEnd(); }, // Fires when the Add to Query button is clicked _addToQueryButtonClick: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Construct a query string based on the currently selected field, operator, and // sample value var query = String.format('[{0}] {1} {2}', task.get_field().alias, task.get_operator(), task.get_sampleValue()); // Add the query string to the end of the query textbox task._queryTextBox.value += query; // Move the cursor in the query textbox to the end task._moveQueryCursorToEnd(); }, // Fires when the Clear Query button is clicked _clearQueryButtonClick: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. task = this.get_task(); // Call method to clear the query textbox task._clearQuery(); }, // Fires when the Execute Query button is clicked _doQueryButtonClick: function() { // Get the client-side task control. We need to do this because "this" refers to // the DOM element that fired the event. var task = this.get_task(); // Get the query from the query textbox var query = task._queryTextBox.value; // Retrieve the fields for the current layer var layer = task.get_layer(); var jsonFields = task.getFieldsFromCache(layer.name, layer.mapResource, layer.map); var fieldArray = Sys.Serialization.JavaScriptSerializer.deserialize(jsonFields); // Place a backslash before single quotes in the query string. This is needed so the // quotes do not prematurely close the callback invocation string. var replaceString = "'"; var replaceRegEx = new RegExp(replaceString, 'g'); query = query.replace(replaceRegEx, "\\'"); // Loop through the current layer's fields. For each, search for the alias in the query // string and replace it with the database field name. We do this because the fields are // shown to the user using any aliases specified, but the database field names must be // used when executing a query. for (var i = 0; i < fieldArray.length; i++) { replaceString = String.format('\\[{0}\\]', fieldArray[i].alias); replaceRegEx = new RegExp(replaceString, 'g'); query = query.replace(replaceRegEx, fieldArray[i].name); } // Show the query execution activity indicator task._executingQueryDiv.style.display = 'inline'; task._doQueryButton.style.display = 'none'; // Call the server-side method that will execute the query. This method includes logic // to add the query results to the TaskResults container. As specified in the task's // client-side initialize function, the _hideQueryingIndicators function is fired when // a result is returned to the client. task.DoQuery(query, layer.id, layer.mapResource); }, //================================================ // Server-Side Method Completion Event Handlers //================================================ // Fires after a call to the server-side GetJsonLayers method has executed _updateLayerDropDown: function(jsonLayerArray) { // Use the AJAX JavaScriptSerializer to create a JavaScript array from the result // returned by GetJsonLayers var layerArray = Sys.Serialization.JavaScriptSerializer.deserialize(jsonLayerArray); // Set the number of options in the layer drop-down list to match the number of // layers in the array this._layerDropDownList.options.length = layerArray.length; // Iterate through the layers, adding each to the drop-down for (var i = 0; i < layerArray.length; i++) { var layer = layerArray[i]; // Set the text of the current drop-down item to be the layer name this._layerDropDownList.options[i].text = layer.name; // Use the JavaScriptSerializer to revert the current layer to a JSON string var jsonLayer = Sys.Serialization.JavaScriptSerializer.serialize(layer); // Store the layer's JSON representation as the current drop-down item's value this._layerDropDownList.options[i].value = jsonLayer; } // Set the drop-down's selected item to the first in the list this._layerDropDownList.selectedIndex = 0; // Turn off layer retrieval activity indicators this._retrievingLayersDiv.style.display = 'none'; this._layerDropDownList.style.display = 'inline'; // Invoke the _onLayerChanged event handler to simulate a new layer being selected. // We invoke the method via ".call" and pass it the layer drop-down so that the // drop-down becomes the method's caller (i.e. references to "this" inside the // method will refer to the drop-down). this._onLayerChanged.call(this._layerDropDownList); }, // Fires after a call to the server-side GetJsonFields method has executed _updateFieldDropDown: function(jsonFieldArray) { // Use the AJAX JavaScriptSerializer to create a JavaScript array from the result // returned by GetJsonFields var fieldArray = Sys.Serialization.JavaScriptSerializer.deserialize(jsonFieldArray); // Set the number of options in the field drop-down list to match the number of // fields in the array this._fieldDropDownList.options.length = fieldArray.length; // Iterate through the fields, adding each to the drop-down for (var i = 0; i < fieldArray.length; i++) { var field = fieldArray[i]; // Set the text of the current drop-down item to be the field alias this._fieldDropDownList.options[i].text = field.alias; // Use the JavaScriptSerializer to revert the current field to a JSON string var jsonField = Sys.Serialization.JavaScriptSerializer.serialize(field); // Store the field's JSON representation as the current drop-down item's value this._fieldDropDownList.options[i].value = jsonField; } // Set the drop-down's selected item to the first in the list this._fieldDropDownList.selectedIndex = 0; // Get the currently selected layer var layer = this.get_layer(); // If the fields have not been cached, cache them now if (!this.getFieldsFromCache(layer.name, layer.mapResource, layer.map)) this.addFieldsToCache(layer.name, layer.mapResource, layer.map, jsonFieldArray); // Turn off field retrieval indicators this._retrievingFieldsDiv.style.display = 'none'; this._fieldDropDownList.style.display = 'inline'; // Invoke the _onFieldChanged event handler to simulate a new field being selected. // We invoke the method via ".call" and pass it the field drop-down so that the // drop-down becomes the method's caller (i.e. references to "this" inside the // method will refer to the drop-down). this._onFieldChanged.call(this._fieldDropDownList); }, // Fires after a call to the server-side GetSampleValues method has executed _updateValueDropDown: function(jsonValueArray) { // Use the AJAX JavaScriptSerializer to create a JavaScript array from the result // returned by GetSampleValues var valueArray = Sys.Serialization.JavaScriptSerializer.deserialize(jsonValueArray); // Get the currently selected field var field = this.get_field(); // Sort the array, passing the numeric sort function if the type of the current field // is numeric if (this.FieldTypes[field.type] == 'Text') valueArray.sort(); else if (this.FieldTypes[field.type] == 'Numeric') valueArray.sort(this._sortNumber); // Set the number of options in the value drop-down list to match the number of // values in the array this._sampleValueDropDownList.options.length = valueArray.length; // Iterate through the values, adding each to the drop-down for (var i = 0; i < valueArray.length; i++) { // If a text field is currently selected, enclose the value in single quotes if (this.FieldTypes[field.type] == 'Text') valueArray[i] = String.format("'{0}'", valueArray[i]); // Specify the sample value as the current item's text and value this._sampleValueDropDownList.options[i].text = valueArray[i]; this._sampleValueDropDownList.options[i].value = valueArray[i]; } // Set the drop-down's selected item to the first in the list this._sampleValueDropDownList.selectedIndex = 0; // Get the current layer var layer = this.get_layer(); // If the values have not been cached, cache them now if (!this.getValuesFromCache(field.name, layer.name, layer.mapResource, layer.map)) this.addValuesToCache(field.name, layer.name, layer.mapResource, layer.map, jsonValueArray); // Turn off the sample values retrieval activity indicator this._retrievingSampleValuesDiv.style.display = 'none'; this._sampleValueDropDownList.style.display = 'inline'; // Invoke the _onSampleValueChanged event handler to simulate a new value being // selected. We invoke the method via ".call" and pass it the sample value drop-down // so that the drop-down becomes the method's caller (i.e. references to "this" inside // the method will refer to the drop-down). this._onSampleValueChanged.call(this._sampleValueDropDownList); }, // Fires after a call to the server-side DoQuery method has executed _hideQueryingIndicators: function() { // Hide query execution activity indicator this._executingQueryDiv.style.display = 'none'; this._doQueryButton.style.display = 'inline'; }, //============================= // Other Private Functions //============================= // Updates the operator drop-down based on the type of the currently selected field. // Called from _onFieldChanged. _updateOperatorList: function() { // Get the currently selected field var selectedField = this.get_field(); var operatorsArray; // Loop through the field types defined in the FieldTypes key-value pair list for (fieldType in this.FieldTypes) { // Check whether the current field type matches that of the selected field if (selectedField.type == fieldType) { // Get the category of the current field type (e.g. Text or Numeric) var fieldCategory = this.FieldTypes[fieldType]; // Construct the name of the operators array to use in populating the drop-down // based on the field category. This assumes that the task contains operators // arrays named <FieldCategory>Operators var operatorsArrayString = String.format('this.{0}Operators', fieldCategory); // Get a reference to the operators array operatorsArray = eval(operatorsArrayString); break; } } // Set the number of items in the operator drop-down to match the size of the // operators array this._operatorDropDownList.options.length = operatorsArray.length; // Populate the drop-down with the operators contained in the array for (var i = 0; i < operatorsArray.length; i++) this._operatorDropDownList.options[i].text = operatorsArray[i]; // Set the first item in the drop-down to be selected this._operatorDropDownList.selectedIndex = 0; // Invoke the _onOperatorChanged event handler to simulate a new operator being // selected. We invoke the method via ".call" and pass it the operator drop-down // so that the drop-down becomes the method's caller (i.e. references to "this" // inside the method will refer to the drop-down). this._onOperatorChanged.call(this._operatorDropDownList); }, // Clears the query textbox _clearQuery: function() { this._queryTextBox.value = ''; this._moveQueryCursorToEnd(); }, // Puts the focus on the query textbox and moves the cursor to the end of any text // within it _moveQueryCursorToEnd: function() { // Get the task's DOM element and make sure it is visible if (this.get_element().style.display == 'none') return; // Set the focus to the query textbox this._queryTextBox.focus(); // Get the last index in the contents of the query textbox endIndex = this._queryTextBox.value.length; // Check whether setSelectionRange is supported if (this._queryTextBox.setSelectionRange) { // Issue a call to setSelectionRange to create a zero-length selction at // the end of the query textbox's contents, effectively moving the cursor // to the end. this._queryTextBox.setSelectionRange(endIndex, endIndex); } else if (this._queryTextBox.createTextRange) { // Since setSelectionRange is not supported, we instead create and // manipulate a TextRange object. The call to createTextRange will // create a range that covers the query textbox's contents. var textRange = this._queryTextBox.createTextRange(); // Move the start of the text range to the end of the textbox's contents, // creating a zero-length selection textRange.moveStart('character', endIndex); // Call select to apply the selection, which moves the cursor to the // selection location - the end of the query text textRange.select(); } }, // Passed into Array.sort() to sort numbers _sortNumber: function(a, b) { return a - b; } } // Register the QueryBuilderTask class, specifying inheritance from ESRI.ADF.UI.FloatingPanel. // Registration is placed in the AJAX init event to ensure that ADF scripts have loaded prior to // the call. Sys.Application.add_init(function registerQueryBuilderTask() { ESRI.ADF.Samples.CustomTasks.QueryBuilderTask.registerClass( 'ESRI.ADF.Samples.CustomTasks.QueryBuilderTask', ESRI.ADF.UI.FloatingPanel); });