Problem with Search with Customization

4451
18
07-09-2015 10:58 AM
EvelynHernandez
Occasional Contributor III

Hello,

Im developing an web app (a form), and in one textfield i wanna put a Street Search using the example on this web(Search with customization | ArcGIS API for JavaScript ).

I have a secured service that im accesing to get the streets that i wanna search, but for some reason it doesnt work.

Im doing something wrong?

My structure folder is:

The login.js: is used to bypass the IdentityManager and have access to another modules like: capas.js (layers are loaded here), comboComuna.js(the combobox that show some cities) and the search (where i wanna add the code for the Calles(streets) and make a search as showed in the example Search with customization).

And my codes for earch file are:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Bootstrap Example</title>
  <meta charset="utf-8">
  <meta name="author" content="ehernanr">
   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="css/bootstrap.min.css">

   <link rel="stylesheet" href="http://js.arcgis.com/3.13/dijit/themes/claro/claro.css">
  
   <link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
  <style>
      #map {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
      }
      body {
        background-color: #FFF;
        overflow: hidden;
        font-family: "Trebuchet MS";
      }
      
      #contenedorMapa{
        height:600px;
      }
      
      #contenedorBtnGrabar {
        vertical-align: middle;
      }
      
    #search {
         display: block;
         position: absolute;
         z-index: 2;
         top: 20px;
         left: 74px;
      }
   
    </style>
   
  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
  
  <!-- Latest compiled JavaScript -->
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
  
   <script>var dojoConfig = {parseOnLoad: true,
                packages: [
                    {
                        name: "js",
                        location: "/js"
                    }
                ]




            };</script>
    
    <script src="http://js.arcgis.com/3.13/"></script>
  
    <script src="js/login.js"></script> 
    <script>
      var map;
      var token;
      var esriId;
      var valorComuna = document.getElementById("id_comuna_dmps");
      
      require(["esri/map", 
              "dojo/parser", 
              "esri/IdentityManager",
              "dojo/domReady!" ], 
      function(Map,parser,IdentityManager) {
        
        parser.parse();
        esriId = IdentityManager;
        
        map = new Map("map", {
        basemap: "topo",
        center: [-71.0659,-32.9252], // long, lat
        zoom: 8
        });
        
        //valida login
        map.onload(validate());
        
       
       
      });
    </script>
  
  
</head>
<body data-spy="scroll"  data-target="form-group" >

<div class="container-fluid">
  <h1>PMS</h1>
  <div class="panel-group">
    
   <div class="panel panel-default"> 
      <div class="panel-body">
          <form class="form-inline" role="form">
              <div class="form-group">
                <label for="email">COMUNA</label>
                <select id="id_comuna_dmps"><option value="" >Seleccione una Comuna</option></select>
              </div>
              <div class="form-group">
                <label>CALLE:</label>
                <!-- <input type="text" class="form-control" id="gl_nombre_calle" size="20" maxlength="30" /> -->
               <div id="search"></div>
              </div>
              <div class="form-group">
                <label>PREFIJO:</label>
                <input type="text" class="form-control" id="gl_num_prefijo"   size="2" maxlength="4" />
              </div>
              
              <div class="form-group">
                <label>NUMERO:</label>
               <input type="text" class="form-control" id="nr_municipal"   size="4" maxlength="6" />
              </div>
              <div class="form-group">
                <label>SUFIJO:</label>
               <input type="text" class="form-control" id="gl_num_sufijo" size="2" maxlength="4" />
              </div>
              <div class="form-group">
                <label>ID DIRECCION:</label>
               <input type="text" class="form-control" id="id_direccion" size="6" />
              </div>
              
              <div class="form-group">
                  <button type="submit" class="btn btn-default">Validar Direccion</button>
                  
              </div>           
          </form>
      </div>
    </div>
    
    
     <div class="panel panel-default"> 
        <div class="panel-body">
           <form class="form-inline" role="form">
              <div class="form-group">
                <label for="email">BUSCAR:</label>
                <select name="lstTipoElemento" id="lstTipoElemento" class="form-control">
                      <option value="camara">CAMARA</option>
                      <option value="poste" selected="selected">POSTE</option>
                      <option value="nis">NIS</option></select>
                
              </div>
               <div class="form-group"><input name="txtValorBusqueda" type="text" id="txtValorBusqueda" class="form-control"  size="60" maxlength="80"></div>
               <div class="form-group"><input name="button" type="button" value="Buscar" class="btn btn-default"></div>
               <div class="form-group"><input name="button2" type="button" value="Manual" class="btn btn-default"></div>
        
           </form>
          
        </div>
       </div>
     </div> 
     
  <div class="panel panel-default"><div class="panel-body" id="contenedorMapa"><div id="map"> </div></div></div>
  <div class="panel panel-default"><div class="panel-body"><div class="form-group" id="contenedorBtnGrabar"><input name="button" type="button" value="Grabar" class="btn btn-default"></div></div></div>
  
    
 </div>  <!-- panel group -->   
  
  
</body>
</html>

login.js:

function validate() {


var username =  'myusername'; 
var password = 'mypassword';

    var http = new XMLHttpRequest();
    var url = "myrestsrv/arcgis/tokens/generateToken";


    var str1 = "username=";
    var str2 = "&password=";
    var str3 = "&f=json&client=requestip&expiration=1440";
    var params = str1.concat(username, str2, password, str3);
    //f="json", client="requestip", expiration="1440"

    http.open("POST", url, true);


    http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    http.setRequestHeader("Content-length", params.length);
    http.setRequestHeader("Connection", "close");


    http.onreadystatechange = function () {




        if (http.readyState == 4 && http.status == 200) {
            var tokenRespuesta = http.responseText;
            token=JSON.parse(tokenRespuesta);
            if (tokenRespuesta.search("error") == -1) {
              
                esri.id.registerToken(  
                 {server:  'http://myRestSrv/arcgis/rest/services',  
                  userId:  'userejemplo',  
                  token:   token.token,  
                  expires: token.expires,  
                  ssl:     false  
                });  
                
                
                //HABILITAR MODULOS Y COMBOS UNA VEZ HECHO EL LOGIN
                dojo.registerModulePath("capas", "http://127.0.0.1:8020/PMS/js/capas");
                dojo.require('capas');
                
                dojo.registerModulePath("comboComuna", "http://127.0.0.1:8020/PMS/js/comboComuna");
                dojo.require('comboComuna');
                
               
                dojo.registerModulePath("mySearch", "http://127.0.0.1:8020/PMS/js/mySearch");
                dojo.require('mySearch');
               
                
        } else {
                alert("Login incorrecto, intente nuevamente.");
            }
        }
    };


    http.send(params);


    return false;
}

comboComuna.js

require([
        "dojo/dom", 
        "esri/tasks/query", "esri/tasks/QueryTask","dojo/domReady!" 
      ], function (dom, Query, QueryTask) {


        var queryTask = new QueryTask("myRestSrv/arcgis/rest/services/Chilquinta_006/Equipos_pto_006/MapServer/0");
        var query = new Query();
        query.returnGeometry = false;
        query.outFields = ["nombre"];
        query.where= "1=1";
        queryTask.execute(query, showResults);
          function showResults (results) {
              document.getElementById("id_comuna_dmps").innerHTML = "";
              var submenu = new Array();
              var resultItems = [];
              var resultCount = results.features.length;
              var x = document.getElementById("id_comuna_dmps");
              
              
              for (var i = 0; i < resultCount; i++) {
                var featureAttributes = results.features.attributes;
                
                for (var attr in featureAttributes) {
                  var c = document.createElement("option");
          
                  c.text = featureAttributes[attr];
                  x.options.add(c, attr);
                }
                //resultItems.push("<br>");
              }
           }//showresults
        });

capas.js

require([
        "esri/map", "esri/InfoTemplate", "esri/layers/FeatureLayer",
        "dojo/parser" , "dojo/domReady!"
      ], function(Map, InfoTemplate, FeatureLayer, parser) {
          parser.parse();
        
          
          var fLayerDist = new FeatureLayer("myRestSrv/arcgis/rest/services/PMS/Concesiones/MapServer/1",{
            mode: FeatureLayer.MODE_ONDEMAND,
            outFields: ["*"],
            
          });
          var fLayerTrans = new FeatureLayer("myRestSrv/arcgis/rest/services/PMS/Concesiones/MapServer/0",{
            mode: FeatureLayer.MODE_ONDEMAND,
            outFields: ["*"],
            
          });
           var fLayerComuna = new FeatureLayer("myRestSrv/arcgis/rest/services/MANTENIMIENTO/MANTENIMIENTO_PODAS/FeatureServer/0", {
            outFields: ["*"]
         });
       
          
          map.addLayers([fLayerDist,fLayerTrans,fLayerComuna]);
         // map.addLayer(fLayerComuna);
        
        
      });

mySearch.js

require([
      "esri/map",
      "esri/layers/FeatureLayer",
      "esri/dijit/Search",
      "esri/InfoTemplate",
      "dojo/domReady!"
      ], function (Map, FeatureLayer, Search, InfoTemplate) {
        


             //Create search widget
         var search = new Search({
            map: map,
            sources: [],
            zoomScale: 5000000
         }, "gl_nombre_calle");


         //listen for the load event and set the source properties 
         search.on("load", function () {


            var sources = search.sources;
            sources.push({
               featureLayer: fLayerComuna,
               placeholder: "123",
               enableLabel: false,
               searchFields: ["CODIGO_SAP"],
               displayField: "CODIGO_SAP",
               exactMatch: false,
               outFields: ["*"],


               //Create an InfoTemplate and include three fields
               infoTemplate: new InfoTemplate("Ecological Footprint", "<a href= ${URL} target=_blank ;'>Additional Info</a></br></br>Country: ${Country}</br>Rating: ${Rating}")


            });
            //Set the sources above to the search widget
            search.set("sources", sources);
         });
         search.startup();
      });

Thanks in advice

Mensaje editado por: Evelyn Hernandez Updated with the search.set("sources", sources);

0 Kudos
18 Replies
thejuskambi
Occasional Contributor III

Couple of thing

the line <script src="http://js.arcgis.com/3.13/"></script>  has to be moved after the script of dojoConfig

there seems to be somthing wrong in the line search.set("gl_nombre_calle", sources); it has to be search.set("sources", sources);

Also the login.js file has to be added after adding arcgis api.

Instead of using require, use define & declare for the modules. so that you have more control on when you initialize the modules.

0 Kudos
EvelynHernandez
Occasional Contributor III

OK, I did it already, but it still doesnt work. What else am i doing wrong?

0 Kudos
thejuskambi
Occasional Contributor III

updated the response.

0 Kudos
EvelynHernandez
Occasional Contributor III

I updated in the mysearch.js how u told me.

require([

      "esri/map",

      "esri/layers/FeatureLayer",

      "esri/dijit/Search",

      "esri/InfoTemplate",

      "dojo/domReady!"

      ], function (Map, FeatureLayer, Search, InfoTemplate) {

       

             //Create search widget

         var search = new Search({

            map: map,

            sources: [],

            zoomScale: 5000000

         }, "gl_nombre_calle");

         //listen for the load event and set the source properties

         search.on("load", function () {

            var sources = search.sources;

            sources.push({

               featureLayer: fLayerComuna,

               placeholder: "123",

               enableLabel: false,

               searchFields: ["CODIGO_SAP"],

               displayField: "CODIGO_SAP",

               exactMatch: false,

               outFields: ["*"],

               //Create an InfoTemplate and include three fields

               infoTemplate: new InfoTemplate("Ecological Footprint", "<a href= ${URL} target=_blank ;'>Additional Info</a></br></br>Country: ${Country}</br>Rating: ${Rating}")

            });

            //Set the sources above to the search widget

            search.set("sources", sources);

         });

         search.startup();

      });

About using define and declare. Can u show me an example using the mysearch.js ? Cuz i still dont understand too much this kind of programming (i have read some tutorials but i still dont get it )

Thanks in advice !

0 Kudos
thejuskambi
Occasional Contributor III

Found one more issue. its the variables. the variables with in the require are local and will not be accessible inside another require. so the fLayerComuna which you have used as featureLayer in the search will not have any value in it. you may want to move the variables outside, if you want to keep the structure as it is.

Check this out on how to write a Class Writing a Class | Guide | ArcGIS API for JavaScript

0 Kudos
EvelynHernandez
Occasional Contributor III

i put the fLayerComuna outside in the capas.js like:

var fLayerComuna;

require([

        "esri/map", "esri/InfoTemplate", "esri/layers/FeatureLayer",

        "dojo/parser" , "dojo/domReady!"

      ], function(Map, InfoTemplate, FeatureLayer, parser) {

          parser.parse();

    

      

          var fLayerDist = new FeatureLayer(""{

            mode: FeatureLayer.MODE_ONDEMAND,

            outFields: ["*"],

        

          });

          var fLayerTrans = new FeatureLayer(,{

            mode: FeatureLayer.MODE_ONDEMAND,

            outFields: ["*"],

        

          });

           fLayerComuna = new FeatureLayer("", {

            outFields: ["*"]

         });

   

      

          map.addLayers([fLayerDist,fLayerTrans,fLayerComuna]);

         // map.addLayer(fLayerComuna);

    

    

      });

I read that webpage already but i still dont get it .

0 Kudos
EvelynHernandez
Occasional Contributor III

I try to change the mySearch.js like this.. idk if its correct. (adding the fLayerComuna as a global variable outside in the capas.js)

define([

      "esri/map",

      "esri/layers/FeatureLayer",

      "esri/dijit/Search",

      "esri/InfoTemplate",

      "dojo/domReady!"

      ], function (Map, FeatureLayer, Search, InfoTemplate) {

        return declare(null, {

          search: new Search({

            map: map,

            sources: [],

            zoomScale: 5000000}, "gl_nombre_calle"),

          

          load: function(){

            var sources = search.sources;

                sources.push({

                  featureLayer: fLayerComuna,

                  placeholder: "123",

                  enableLabel: false,

                  searchFields: ["CODIGO_SAP"],

                  displayField: "CODIGO_SAP",

                  exactMatch: false,

                  outFields: ["*"],

    //Create an InfoTemplate and include three fields

                  infoTemplate: new InfoTemplate("Ecological Footprint", "<a href= ${URL} target=_blank ;'>Additional Info</a></br></br>Country: ${Country}</br>Rating: ${Rating}")

  

                });

            search.set("sources", sources);

          

          } ,

          startUp: function (){

            search.startup();

          }

        

        

        });

});

Thanks in advice.

0 Kudos
thejuskambi
Occasional Contributor III

I tried to create module for you. Its not tested as I cannot acces the services.

From what I understand, you dont need multiple files so I combined them into one. In the validate, instead of all the dojo.require. add this and let me know if it works.

var mapUtilities;
require(["js/myModule"], function(MyModule){
    mapUtilities = new MyModule(map);
    mapUtilities.initLayers();
    mapUtilities.initOptions();
    mapUtilities.initSearch();   
});

Hope this was helpfule. Use developer tools to debug and ensure things are working.

0 Kudos
EvelynHernandez
Occasional Contributor III

Now i have this, but it still doesnt work.

<!DOCTYPE html>
<html lang="en">
<head>
  <title>Bootstrap Example</title>
  <meta charset="utf-8">
  <meta name="author" content="ehernanr">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="css/bootstrap.min.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.13/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css">
  <style>
      #map {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
      }
      body {
        background-color: #FFF;
        overflow: hidden;
        font-family: "Trebuchet MS";
      }
     
      #contenedorMapa{
        height:600px;
      }
     
      #contenedorBtnGrabar {
        vertical-align: middle;
      }
     
    #search {
        display: block;
        position: absolute;
        z-index: 2;
        top: 20px;
        left: 74px;
      }

    </style>

  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

  <!-- Latest compiled JavaScript -->
  <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>

  <script>var dojoConfig = {parseOnLoad: true,
                packages: [
                    {
                        name: "js",
                        location: "/js"
                    }
                ]




            };</script>
   
    <script src="http://js.arcgis.com/3.13/"></script>

    <script src="js/login.js"></script>
    <script>
      var map;
      var token;
      var esriId;
      var valorComuna = document.getElementById("id_comuna_dmps");
     
      require(["esri/map",
              "dojo/parser",
              "esri/IdentityManager",
              "dojo/domReady!" ],
      function(Map,parser,IdentityManager) {
       
        parser.parse();
        esriId = IdentityManager;
       
        map = new Map("map", {
        basemap: "topo",
        center: [-71.0659,-32.9252], // long, lat
        zoom: 8
        });
       
        //valida login
        map.onload(validate());
       
     
     
      });
    </script>


</head>
<body data-spy="scroll"  data-target="form-group" >

<div class="container-fluid">
  <h1>PMS</h1>
  <div class="panel-group">
   
  <div class="panel panel-default">
      <div class="panel-body">
          <form class="form-inline" role="form">
              <div class="form-group">
                <label for="email">COMUNA</label>
                <select id="id_comuna_dmps"><option value="" >Seleccione una Comuna</option></select>
              </div>
              <div class="form-group">
                <label>CALLE:</label>
                <!-- <input type="text" class="form-control" id="gl_nombre_calle" size="20" maxlength="30" /> -->
              <div id="search"></div>
              </div>
              <div class="form-group">
                <label>PREFIJO:</label>
                <input type="text" class="form-control" id="gl_num_prefijo"  size="2" maxlength="4" />
              </div>
             
              <div class="form-group">
                <label>NUMERO:</label>
              <input type="text" class="form-control" id="nr_municipal"  size="4" maxlength="6" />
              </div>
              <div class="form-group">
                <label>SUFIJO:</label>
              <input type="text" class="form-control" id="gl_num_sufijo" size="2" maxlength="4" />
              </div>
              <div class="form-group">
                <label>ID DIRECCION:</label>
              <input type="text" class="form-control" id="id_direccion" size="6" />
              </div>
             
              <div class="form-group">
                  <button type="submit" class="btn btn-default">Validar Direccion</button>
                 
              </div>         
          </form>
      </div>
    </div>
   
   
    <div class="panel panel-default">
        <div class="panel-body">
          <form class="form-inline" role="form">
              <div class="form-group">
                <label for="email">BUSCAR:</label>
                <select name="lstTipoElemento" id="lstTipoElemento" class="form-control">
                      <option value="camara">CAMARA</option>
                      <option value="poste" selected="selected">POSTE</option>
                      <option value="nis">NIS</option></select>
               
              </div>
              <div class="form-group"><input name="txtValorBusqueda" type="text" id="txtValorBusqueda" class="form-control"  size="60" maxlength="80"></div>
              <div class="form-group"><input name="button" type="button" value="Buscar" class="btn btn-default"></div>
              <div class="form-group"><input name="button2" type="button" value="Manual" class="btn btn-default"></div>
       
          </form>
         
        </div>
      </div>
    </div>
   
  <div class="panel panel-default"><div class="panel-body" id="contenedorMapa"><div id="map"> </div></div></div>
  <div class="panel panel-default"><div class="panel-body"><div class="form-group" id="contenedorBtnGrabar"><input name="button" type="button" value="Grabar" class="btn btn-default"></div></div></div>

   
</div>  <!-- panel group --> 


</body>
</html>

myModule.js

define([ "dojo/_base/declare",

  "dojo/_base/lang",

    "esri/map",

  "esri/InfoTemplate",

  "esri/layers/FeatureLayer",

  "esri/dijit/Search",

  "dojo/dom",

    "esri/tasks/query",

  "esri/tasks/QueryTask"],

  function(declare,

  lang,

  Map,

  InfoTemplate,

  FeatureLayer,

  Search,

  dom,

  Query,

  QueryTask) {

        return declare(null, {

  search:null,

  map: null,

  fLayerDist: null,

  fLayerTrans: null,

  fLayerComuna: null,

  constructor: function(map){

  this.map = map;

  },

  initLayers: function(){

  this.fLayerDist = new FeatureLayer("",{ 

  mode: FeatureLayer.MODE_ONDEMAND,

  outFields: ["*"],

   });

  this.fLayerTrans = new FeatureLayer("",

  mode: FeatureLayer.MODE_ONDEMAND,

  outFields: ["*"],

   });

  this.fLayerComuna = new FeatureLayer("",

  outFields: ["*"]

  });

  this.map.addLayers([this.fLayerDist,this.fLayerTrans,this.fLayerComuna]);

  // map.addLayer(fLayerComuna);

  },

  initSearch: function(){

  this.search = new Search({

  map: this.map,

  sources: [],

  zoomScale: 5000000

  }, "gl_nombre_calle");

  //listen for the load event and set the source properties

  this.search.on("load", lang.hitch(this, function(){

  var sources = search.sources;

  sources.push({

  featureLayer: this.fLayerComuna,

  placeholder: "123",

  enableLabel: false,

  searchFields: ["CODIGO_SAP"],

  displayField: "CODIGO_SAP",

  exactMatch: false,

  outFields: ["*"],

  //Create an InfoTemplate and include three fields

  infoTemplate: new InfoTemplate("Ecological Footprint", "<a href= ${URL} target=_blank ;'>Additional Info</a></br></br>Country: ${Country}</br>Rating: ${Rating}")

  });

  //Set the sources above to the search widget

  this.search.set("sources", sources);

  }));

  this.search.startup();

  },

  initOptions: function(){

  var queryTask = new QueryTask(""); 

  var query = new Query();

  query.returnGeometry = false;

  query.outFields = ["nombre"];

  query.where= "1=1";

  queryTask.execute(query, showResults);

   function showResults (results) {

   document.getElementById("id_comuna_dmps").innerHTML = "";

   var submenu = new Array();

   var resultItems = [];

   var resultCount = results.features.length;

   var x = document.getElementById("id_comuna_dmps");

   for (var i = 0; i < resultCount; i++) {

  var featureAttributes = results.features.attributes;

  for (var attr in featureAttributes) {

   var c = document.createElement("option");

   c.text = featureAttributes[attr];

   x.options.add(c, attr);

  }

  //resultItems.push("<br>");

   }

    }//showresults

  }

  });

login.js

var mapUtilities;  
function validate() {


var username =  'user'; 
var password = 'password;

    var http = new XMLHttpRequest();
    var url = "/tokens/generateToken";


    var str1 = "username=";
    var str2 = "&password=";
    var str3 = "&f=json&client=requestip&expiration=1440";
    var params = str1.concat(username, str2, password, str3);
    //f="json", client="requestip", expiration="1440"

    http.open("POST", url, true);


    http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    http.setRequestHeader("Content-length", params.length);
    http.setRequestHeader("Connection", "close");


    http.onreadystatechange = function () {




        if (http.readyState == 4 && http.status == 200) {
            var tokenRespuesta = http.responseText;
            token=JSON.parse(tokenRespuesta);
            if (tokenRespuesta.search("error") == -1) {
              
                esri.id.registerToken(  
                 {server:  ',  
                  userId:  'userejemplo',  
                  token:   token.token,  
                  expires: token.expires,  
                  ssl:     false  
                });  
                
                
                
                  require(["js/myModule"], function(MyModule){  
                      mapUtilities = new MyModule(map);  
                      
                      mapUtilities.initLayers();  
                      mapUtilities.initOptions();  
                      mapUtilities.initSearch();     
                  }); 
               
                
        } else {
                alert("Incorrect Login , try again");
            }
        }
    };


    http.send(params);


    return false;




}

I really appreciate ur help on this.

Its like the requite when u try to load the module (mymodule) it doesnt recognize the module or something cuz it doesnt load the content, idk why

0 Kudos