Usare il motore di search di SharePoint con Javascript e Knockout
Un esempio di come è possibile interrogare il web service del motore di ricerca di SharePoint 2010 Standard o Enterprise usando JavaScript, jQuery e Knockout js.
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.
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-*.
A seguire il codice JavaScript dove:
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.
HTML
<!-- 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-*.
HTML
<!-- 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()
JavaScript
<!-- 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 js). Successivamente caricare il file in una doc lib di SharePoint e infine, visualizzarlo tramite una content editor webpart che punta al file SgartSearch.html.