Un esempio di come è possibile interrogare il web service del motore di ricerca di SharePoint 2010 Standard o Enterprise usando javascript, jQuery e Knokout.

Uso la libreria knockout per la visualizzazione dei risultati in quanto implementa il pattern Model-View-View Mode (MVVM) in javascript (MVVM è presente, ad esempio, in silverlight o WPF). La libreria permette di avere il binding bidirezionale tra la view e il View Model, questo consente di modificare il view model sgartExecuteSearchViewModel e ottenere l'aggiornamento automatico della view, quindi dell'html. Il tutto funziona tramite le proprietà o collection di tipo observable (ko.observable() e ko.observableArray()) e gli attributi di binding data-* presenti nella view.

L'esempio per funzionare richiede le librerie jQuery e knockout.
<!-- rimuovere questi link se sono gia' presenti nella master page queste librerie --->
<script type="text/javascript" src="/_layouts/Sgart/knockout-2.1.0.js"></script>
<script type="text/javascript" src="/_layouts/Sgart/jquery-1.8.2.min.js"></script>

La prima cosa da fare è definire la view ovvero il template di visualizzazione in HTML e il relativo binding delle proprietà realizzato tramite l'attributo data-*.
<!-- view html -->
<div id="sgartExecuteSearch">
  <div>
    Words to search: <input type="text" id="sgartExecuteSearchResultName" /> <img src="/_layouts/IMAGES/pickerprogressbar.gif" alt="updating..." data-bind="visible: updating() == true" style="display:none;" />
  </div>

  <div id="sgartExecuteSearchResult" style="border:1px solid gray; display:none;" data-bind="visible: items().length > 0">
	  <h2>Search results</h2>
	  <ul data-bind="foreach: items">
		  <li><a data-bind="text: name, attr: {href: url}" target="_blank"></a>
		  <div data-bind="text: description"></div></li>
	  </ul>
	  <div>Items: <span data-bind="text: items().length"></span></div>
  </div>
  <div style="color:red; display:none;" data-bind="visible: error().length > 0">Error: <span data-bind="text: error"></span></div>
</div>

A seguire il codice javascript dove:
  • è definita la classe che rappresenta il View Model sgartExecuteSearchViewModel
  • viene fatto il binding tra la view e il view model ko.applyBindings(sgartExecuteSearchVM, view);
  • viene aggiunto l'evento keyup sulla textbox per eseguire la ricerca (sgartExecuteSearchServer) con un ritardo di 500ms
  • nel metodo che esegue la ricerca viene costruita la query xml per il motore di ricerca, il messaggio soap xml ed eseguita la query asincrona al web service soap /_vti_bin/search.asmx tramite il metodo jQuery $.ajax()
  • la funzione sgartProcessResult gestisce il risultato del web service e popola la collection obeservable sgartExecuteSearchVM.items() tramite il metodo sgartExecuteSearchVM.addItem()

<!-- script con il ViewModel e il binding -->
<script type="text/javascript">

ExecuteOrDelayUntilScriptLoaded(sgartExecuteSearchInit, "sp.js"); 

var sgartExecuteSearchTimer = null; //timer
var sgartExecuteSearchVM = null;  //ViewModel

// ViewModel
function sgartExecuteSearchViewModel() {
	var self = this;
	self.error = ko.observable("");
	self.updating = ko.observable(false);
	//array con i risultati della ricerca
	self.items = ko.observableArray();   
	
	// Operations
	self.addItem = function(sName, sUrl, sDescription) {
	    self.items.push({name: sName, url: sUrl, description: sDescription});
	} 
	self.removeAllItems = function() {
		self.items.removeAll();
	}
	//campo calcolato con il totale degli items
	self.totalItems = ko.computed(function(){
		return self.items.length;
	});
	
	//non binding parameters
	self.minLength = 4; //numero di caratteri minimo per eseguire la ricerca
	self.numberOfResults = 10;  //numero di risultati massimo da restituire
}

// init search
function sgartExecuteSearchInit(){
	//recupero la View
	var view = document.getElementById("sgartExecuteSearch");
	
	//creo il ViewModel
	sgartExecuteSearchVM = new sgartExecuteSearchViewModel();
	//faccio il binding tra la view e il ViewModel
	ko.applyBindings(sgartExecuteSearchVM, view);
	
	//aggiungo l'evento keyup alla textbox
	$('#sgartExecuteSearchResultName').keyup(function(){
		//per ogni tasto resetto il timer
		clearTimeout(sgartExecuteSearchTimer);
		//e lo reimposto a mezzo secondo, allo scadere del tempo parte la ricerca
		sgartExecuteSearchTimer = setTimeout("sgartExecuteSearchServer()", 500); 
	});
}
  
// usa il motore di ricerca
function sgartExecuteSearchServer() { 
  //recupero le keyword di ricerca
	var query = $("#sgartExecuteSearchResultName").val();

  //resetto gli items nel ViewModel
	sgartExecuteSearchVM.removeAllItems();
	sgartExecuteSearchVM.error("");
	
  //se ho meno di 4 caratteri non eseguo la ricerca
	if(query.length < sgartExecuteSearchVM.minLength)
		return;

  //query per il motore di ricerca
  var queryXML =  
      "<QueryPacket xmlns='urn:Microsoft.Search.Query' Revision='1000'>" +
        "<Query domain='QDomain'>" +
          "<SupportedFormats><Format>urn:Microsoft.Search.Response.Document.Document</Format></SupportedFormats>" + 
          "<Context>" + 
            "<QueryText language='en-US' type='STRING'>ANY(" + query + ")</QueryText>" +
          "</Context>" +
          "<SortByProperties><SortByProperty name='Rank' direction='Descending' order='1'/></SortByProperties>" +
          "<Range><StartAt>1</StartAt><Count>" + sgartExecuteSearchVM.numberOfResults + "</Count></Range>" +
          "<EnableStemming>false</EnableStemming>" +
          "<TrimDuplicates>true</TrimDuplicates>" +
          "<IgnoreAllNoiseQuery>true</IgnoreAllNoiseQuery>" +
          "<ImplicitAndBehavior>true</ImplicitAndBehavior>" +
          "<IncludeRelevanceResults>true</IncludeRelevanceResults>" +
          "<IncludeSpecialTermResults>true</IncludeSpecialTermResults>" +
          "<IncludeHighConfidenceResults>true</IncludeHighConfidenceResults>" +
        "</Query>" + 
      "</QueryPacket>"; 
 
  //quesry soap per il web service /_vti_bin/search.asmx
  var soapEnv = 
       "<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>" +
         "<soap:Body>" + 
           "<Query xmlns='urn:Microsoft.Search'>" + 
             "<queryXml>" + escapeHTML(queryXML) + "</queryXml>" + 
           "</Query>" + 
         "</soap:Body>" +  
       "</soap:Envelope>"; 

  //eseguo la chiamata ajax asincrona
  sgartExecuteSearchVM.updating(true);

  $.ajax({ 
      url: "/_vti_bin/search.asmx", 
      type: "POST", 
      dataType: "xml", 
      data: soapEnv,  
      contentType: "text/xml; charset=\"utf-8\"",
      complete: sgartSearchProcessResult,
      error: function(XMLHttpRequest, textStatus, errorThrown) {
        sgartExecuteSearchVM.error(textStatus);
        sgartExecuteSearchVM.updating(true);
		  }
  });    
}   
    
function sgartSearchProcessResult(xData, status) { 
	if(status != "success"){
        sgartExecuteSearchVM.error("Error: " + status);
	}else{
		//processo i risultato
		$(xData.responseXML).find("QueryResult").each(function() {   
		    var x = $("<xml>" + $(this).text() + "</xml>"); 
			if($("Status", x).text()!="SUCCESS"){
		        sgartExecuteSearchVM.error($(this).text());
			}else{
				x.find("Document").each(function() { 
				  var node =  $(this);
				  var title = $("Title", node).text(); 
				  var url = $("Action>LinkUrl", node).text(); 
				  var description = $("Description", node).text();
				  //aggiunge l'item alla observable collection 
				  sgartExecuteSearchVM.addItem(title, url, description);
				});
			}
		  });
	}	 
	sgartExecuteSearchVM.updating(false);
} 
 
function escapeHTML (str) { 
  return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); 
}    
</script>
Per utilizzarlo è sufficiente creare un file SgartSearch.html con dentro sia la view html che il codice javascript (e gli eventuali riferimenti alle librerie jQuery e Knockout). Successivamente caricare il file in una doc lib di SharePoint e infine, visualizzarlo tramite una content editor webpart che punta al file SgartSearch.html.