Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Informazioni su come implementare due diversi sistemi di paging, il primo basato sui numeri di pagina e il secondo sullo scorrimento infinito. Entrambi i sistemi di paging sono ampiamente usati e la selezione di quella corretta dipende dall'esperienza utente desiderata con i risultati.
Questa esercitazione illustra come:
- Estendere l'app con paginazione numerata
- Estendi la tua app con lo scorrimento infinito
Informazioni generali
Questa esercitazione sovrappone un sistema di paging in un progetto creato in precedenza descritto nell'esercitazione Creare la prima app di ricerca .
Le versioni completate del codice che verranno sviluppate in questa esercitazione sono disponibili nei progetti seguenti:
Prerequisiti
- Progetto 1-basic-search-page (GitHub). Questo progetto può essere una versione personalizzata creata dall'esercitazione precedente o una copia da GitHub.
Estendere l'app con paging numerato
Il paging numerato è il sistema di paging scelto per i principali motori di ricerca Web commerciali e molti altri siti Web di ricerca. Il paging numerato include in genere un'opzione "successivo" e "precedente" oltre a un intervallo di numeri di pagina effettivi. Potrebbe anche essere disponibile un'opzione "prima pagina" e "ultima pagina". Queste opzioni offrono certamente un controllo utente sull'esplorazione dei risultati basati su pagine.
In questa esercitazione si aggiungerà un sistema che include prima, precedente, successiva e ultima opzione, insieme ai numeri di pagina che non iniziano da 1, ma racchiudere invece la pagina corrente in cui si trova l'utente (ad esempio, se l'utente sta esaminando la pagina 10, ad esempio, i numeri di pagina 8, 9, 10, 11 e 12 vengono visualizzati).
Il sistema sarà sufficientemente flessibile per consentire l'impostazione del numero di numeri di pagina visibili in una variabile globale.
Il sistema considererà i pulsanti di numero di pagina più a sinistra e destro come speciali, ovvero attiveranno la modifica dell'intervallo di numeri di pagina visualizzati. Ad esempio, se vengono visualizzati i numeri di pagina 8, 9, 10, 11 e 12 e l'utente fa clic su 8, l'intervallo di numeri di pagina visualizzati cambia in 6, 7, 8, 9 e 10. E c'è uno spostamento simile a destra se hanno selezionato 12.
Aggiungere campi di paging al modello
Assicurati che la soluzione della pagina di ricerca di base sia aperta.
Aprire il file del modello SearchData.cs.
Aggiungere variabili globali per supportare l'impaginazione. In MVC le variabili globali vengono dichiarate nella propria classe statica. ResultsPerPage imposta il numero di risultati per pagina. MaxPageRange determina il numero di numeri di pagina visibili nella visualizzazione. PageRangeDelta determina il numero di pagine da spostare a sinistra o a destra quando è selezionato il numero di pagina più a sinistra o a destra. In genere questo secondo numero è circa la metà di MaxPageRange. Aggiungi il codice seguente nel namespace.
public static class GlobalVariables { public static int ResultsPerPage { get { return 3; } } public static int MaxPageRange { get { return 5; } } public static int PageRangeDelta { get { return 2; } } }Suggerimento
Se si esegue questo progetto in un dispositivo con uno schermo più piccolo, ad esempio un portatile, prendere in considerazione la modifica di ResultsPerPage su 2.
Aggiungere proprietà di paging alla classe SearchData , dopo la proprietà searchText .
// The current page being displayed. public int currentPage { get; set; } // The total number of pages of results. public int pageCount { get; set; } // The left-most page number to display. public int leftMostPage { get; set; } // The number of page numbers to display - which can be less than MaxPageRange towards the end of the results. public int pageRange { get; set; } // Used when page numbers, or next or prev buttons, have been selected. public string paging { get; set; }
Aggiungere una tabella di opzioni di paging alla visualizzazione
Aprire il file index.cshtml e aggiungere il codice seguente subito prima del tag /body> di chiusura<. Questo nuovo codice presenta una tabella di opzioni di paginazione: prima, precedente, 1, 2, 3, 4, 5, successiva, ultima.
@if (Model != null && Model.pageCount > 1) { // If there is more than one page of results, show the paging buttons. <table> <tr> <td> @if (Model.currentPage > 0) { <p class="pageButton"> @Html.ActionLink("|<", "Page", "Home", new { paging = "0" }, null) </p> } else { <p class="pageButtonDisabled">|<</p> } </td> <td> @if (Model.currentPage > 0) { <p class="pageButton"> @Html.ActionLink("<", "PageAsync", "Home", new { paging = "prev" }, null) </p> } else { <p class="pageButtonDisabled"><</p> } </td> @for (var pn = Model.leftMostPage; pn < Model.leftMostPage + Model.pageRange; pn++) { <td> @if (Model.currentPage == pn) { // Convert displayed page numbers to 1-based and not 0-based. <p class="pageSelected">@(pn + 1)</p> } else { <p class="pageButton"> @Html.ActionLink((pn + 1).ToString(), "PageAsync", "Home", new { paging = @pn }, null) </p> } </td> } <td> @if (Model.currentPage < Model.pageCount - 1) { <p class="pageButton"> @Html.ActionLink(">", "PageAsync", "Home", new { paging = "next" }, null) </p> } else { <p class="pageButtonDisabled">></p> } </td> <td> @if (Model.currentPage < Model.pageCount - 1) { <p class="pageButton"> @Html.ActionLink(">|", "PageAsync", "Home", new { paging = Model.pageCount - 1 }, null) </p> } else { <p class="pageButtonDisabled">>|</p> } </td> </tr> </table> }Usiamo una tabella HTML per allineare le cose in modo ordinato. Tuttavia, tutte le azioni provengono dalle @Html.ActionLink istruzioni, ognuna delle quali chiama il controller con un nuovo modello creato con voci diverse per la proprietà di paginazione aggiunta in precedenza.
Le opzioni della prima e dell'ultima pagina non inviano stringhe come "first" e "last", ma invece inviano i numeri di pagina corretti.
Aggiungere classi di paging all'elenco di stili HTML nel file hotels.css. La classe pageSelected è disponibile per identificare la pagina corrente (applicando un formato grassetto al numero di pagina) nell'elenco dei numeri di pagina.
.pageButton { border: none; color: darkblue; font-weight: normal; width: 50px; } .pageSelected { border: none; color: black; font-weight: bold; width: 50px; } .pageButtonDisabled { border: none; color: lightgray; font-weight: bold; width: 50px; }
Aggiungere un'azione Pagina al controller
Aprire il file HomeController.cs e aggiungere l'azione PageAsync . Questa azione risponde a una qualsiasi delle opzioni di pagina selezionate.
public async Task<ActionResult> PageAsync(SearchData model) { try { int page; switch (model.paging) { case "prev": page = (int)TempData["page"] - 1; break; case "next": page = (int)TempData["page"] + 1; break; default: page = int.Parse(model.paging); break; } // Recover the leftMostPage. int leftMostPage = (int)TempData["leftMostPage"]; // Recover the search text and search for the data for the new page. model.searchText = TempData["searchfor"].ToString(); await RunQueryAsync(model, page, leftMostPage); // Ensure Temp data is stored for next call, as TempData only stores for one call. TempData["page"] = (object)page; TempData["searchfor"] = model.searchText; TempData["leftMostPage"] = model.leftMostPage; } catch { return View("Error", new ErrorViewModel { RequestId = "2" }); } return View("Index", model); }Il metodo RunQueryAsync mostrerà ora un errore di sintassi, a causa del terzo parametro, che verrà visualizzato in un po'.
Annotazioni
Le chiamate a TempData archiviano un valore (un oggetto) nella risorsa di archiviazione temporanea, anche se questa risorsa di archiviazione persiste per una sola chiamata. Se si archivia un elemento in dati temporanei, sarà disponibile per la chiamata successiva a un'azione del controller, ma sarà sicuramente scomparso dalla chiamata successiva. Archiviamo il testo di ricerca e le proprietà di impaginazione di nuovo nell'archivio temporaneo ogni volta che viene chiamato PageAsync a causa di questa breve durata.
Aggiornare l'azione Index(model) per archiviare le variabili temporanee e aggiungere il parametro di pagina più a sinistra alla chiamata RunQueryAsync .
public async Task<ActionResult> Index(SearchData model) { try { // Ensure the search string is valid. if (model.searchText == null) { model.searchText = ""; } // Make the search call for the first page. await RunQueryAsync(model, 0, 0); // Ensure temporary data is stored for the next call. TempData["page"] = 0; TempData["leftMostPage"] = 0; TempData["searchfor"] = model.searchText; } catch { return View("Error", new ErrorViewModel { RequestId = "1" }); } return View(model); }Il metodo RunQueryAsync , introdotto nella lezione precedente, richiede modifiche per risolvere l'errore di sintassi. I campi Skip, Size e IncludeTotalCount della classe SearchOptions vengono usati per richiedere solo una pagina di risultati, a partire dall'impostazione Skip . È anche necessario calcolare le variabili di paginazione per la nostra visualizzazione. Sostituire l'intero metodo con il codice seguente.
private async Task<ActionResult> RunQueryAsync(SearchData model, int page, int leftMostPage) { InitSearch(); var options = new SearchOptions { // Skip past results that have already been returned. Skip = page * GlobalVariables.ResultsPerPage, // Take only the next page worth of results. Size = GlobalVariables.ResultsPerPage, // Include the total number of results. IncludeTotalCount = true }; // Add fields to include in the search results. options.Select.Add("HotelName"); options.Select.Add("Description"); // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search. model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false); // This variable communicates the total number of pages to the view. model.pageCount = ((int)model.resultList.TotalCount + GlobalVariables.ResultsPerPage - 1) / GlobalVariables.ResultsPerPage; // This variable communicates the page number being displayed to the view. model.currentPage = page; // Calculate the range of page numbers to display. if (page == 0) { leftMostPage = 0; } else if (page <= leftMostPage) { // Trigger a switch to a lower page range. leftMostPage = Math.Max(page - GlobalVariables.PageRangeDelta, 0); } else if (page >= leftMostPage + GlobalVariables.MaxPageRange - 1) { // Trigger a switch to a higher page range. leftMostPage = Math.Min(page - GlobalVariables.PageRangeDelta, model.pageCount - GlobalVariables.MaxPageRange); } model.leftMostPage = leftMostPage; // Calculate the number of page numbers to display. model.pageRange = Math.Min(model.pageCount - leftMostPage, GlobalVariables.MaxPageRange); return View("Index", model); }Infine, apportare una piccola modifica alla visualizzazione. La variabile resultList.Results.TotalCount conterrà ora il numero di risultati restituiti in una pagina (3 nell'esempio), non il numero totale. Poiché è stato impostato IncludeTotalCount su true, la variabile resultList.TotalCount contiene ora il numero totale di risultati. Individuare quindi dove viene visualizzato il numero di risultati nella visualizzazione e modificarlo nel codice seguente.
// Show the result count. <p class="sampleText"> @Model.resultList.TotalCount Results </p> var results = Model.resultList.GetResults().ToList(); @for (var i = 0; i < results.Count; i++) { // Display the hotel name and description. @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" }) @Html.TextArea($"desc{1}", results[i].Document.Description, new { @class = "box2" }) }Annotazioni
Quando si imposta IncludeTotalCount su true, si verifica un lieve calo delle prestazioni, perché questo totale deve essere calcolato da Ricerca Cognitiva di Azure. Con set di dati complessi, viene visualizzato un avviso che indica che il valore restituito è un'approssimazione. Poiché il corpus di ricerca dell'hotel è piccolo, sarà accurato.
Compilare ed eseguire l'app
Ora selezionare Avvia senza eseguire debug oppure premere il tasto F5.
Ricerca su una stringa che restituisce un sacco di risultati (ad esempio "wifi"). È possibile scorrere ordinatamente i risultati?
Prova a fare clic sui numeri di pagina più a destra e poi su quelli più a sinistra. I numeri di pagina si adattano in modo appropriato al centro della pagina in cui ci si trovi?
Le opzioni "first" e "last" sono utili? Alcuni motori di ricerca commerciali usano queste opzioni e altri no.
Passare all'ultima pagina dei risultati. L'ultima pagina è l'unica pagina che può contenere meno dei risultati di ResultsPerPage .
Digitare "town" e clicca su "cerca". Non vengono visualizzate opzioni di paging se i risultati sono inferiori a una pagina.
Salva questo progetto e continua con la sezione successiva per una forma alternativa di paginazione.
Amplia la tua app con lo scorrimento infinito
Lo scorrimento infinito viene attivato quando un utente scorre una barra di scorrimento verticale fino all'ultimo dei risultati visualizzati. In questo caso, viene effettuata una chiamata al servizio di ricerca per la pagina successiva dei risultati. Se non sono presenti altri risultati, non viene restituito alcun risultato e la barra di scorrimento verticale non cambia. Se sono presenti più risultati, vengono aggiunti alla pagina corrente e la barra di scorrimento cambia per mostrare che sono disponibili più risultati.
Un punto importante da notare è che la pagina corrente non viene sostituita, ma piuttosto estesa per mostrare i risultati aggiuntivi. Un utente può sempre scorrere verso l'alto fino ai primi risultati della ricerca.
Per implementare lo scorrimento infinito, iniziamo con il progetto prima che venissero aggiunti gli elementi di scorrimento dei numeri di pagina. In GitHub questa è la soluzione FirstAzureSearchApp .
Aggiungere campi di paginazione al modello
Aggiungere prima di tutto una proprietà di paging alla classe SearchData (nel file del modello di SearchData.cs).
// Record if the next page is requested. public string paging { get; set; }Questa variabile è una stringa che contiene "next" se la pagina successiva dei risultati deve essere inviata o essere null per la prima pagina di una ricerca.
Nello stesso file e all'interno dello spazio dei nomi aggiungere una classe variabile globale con una sola proprietà. In MVC le variabili globali vengono dichiarate nella propria classe statica. ResultsPerPage imposta il numero di risultati per pagina.
public static class GlobalVariables { public static int ResultsPerPage { get { return 3; } } }
Aggiungere una barra di scorrimento verticale alla visualizzazione
Individuare la sezione del file index.cshtml che visualizza i risultati (inizia con il @if (Modello != null)).
Sostituire la sezione con il codice seguente. La nuova <sezione div> si trova intorno all'area che deve essere scorrevole e aggiunge sia un attributo overflow-y che una chiamata a una funzione onscroll denominata "scrolled()", come in questo caso.
@if (Model != null) { // Show the result count. <p class="sampleText"> @Model.resultList.TotalCount Results </p> var results = Model.resultList.GetResults().ToList(); <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()"> <!-- Show the hotel data. --> @for (var i = 0; i < results.Count; i++) { // Display the hotel name and description. @Html.TextAreaFor(m => results[i].Document.HotelName, new { @class = "box1" }) @Html.TextArea($"desc{i}", results[i].Document.Description, new { @class = "box2" }) }Direttamente sotto il ciclo, dopo il tag </div>, aggiungi la funzione scrolled.
<script> function scrolled() { if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) { $.getJSON("/Home/NextAsync", function (data) { var div = document.getElementById('myDiv'); // Append the returned data to the current list of hotels. for (var i = 0; i < data.length; i += 2) { div.innerHTML += '\n<textarea class="box1">' + data[i] + '</textarea>'; div.innerHTML += '\n<textarea class="box2">' + data[i + 1] + '</textarea>'; } }); } } </script>L'istruzione if nello script precedente verifica se l'utente ha eseguito lo scorrimento nella parte inferiore della barra di scorrimento verticale. In caso affermativo, viene effettuata una chiamata al controller Home a un'azione denominata NextAsync. Nessun'altra informazione è necessaria per il controllore, che restituirà la pagina successiva dei dati. Questi dati vengono quindi formattati usando stili HTML identici della pagina originale. Se non vengono restituiti risultati, non viene aggiunto alcun elemento e gli elementi rimangono così come sono.
Gestire l'azione Successiva
Esistono solo tre azioni che devono essere inviate al controller: la prima esecuzione dell'app, che chiama Index(), la prima ricerca eseguita dall'utente, che chiama Index(model) e quindi le chiamate successive per ottenere altri risultati tramite Next(model).
Aprire il file del controller della home ed eliminare il metodo RunQueryAsync dal tutorial originale.
Sostituire l'azione Index(model) con il codice seguente. Ora gestisce il campo paging quando è null o impostato su "next" e si occupa della chiamata a Azure Cognitive Search.
public async Task<ActionResult> Index(SearchData model) { try { InitSearch(); int page; if (model.paging != null && model.paging == "next") { // Increment the page. page = (int)TempData["page"] + 1; // Recover the search text. model.searchText = TempData["searchfor"].ToString(); } else { // First call. Check for valid text input. if (model.searchText == null) { model.searchText = ""; } page = 0; } // Setup the search parameters. var options = new SearchOptions { SearchMode = SearchMode.All, // Skip past results that have already been returned. Skip = page * GlobalVariables.ResultsPerPage, // Take only the next page worth of results. Size = GlobalVariables.ResultsPerPage, // Include the total number of results. IncludeTotalCount = true }; // Specify which fields to include in results. options.Select.Add("HotelName"); options.Select.Add("Description"); // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search. model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options).ConfigureAwait(false); // Ensure TempData is stored for the next call. TempData["page"] = page; TempData["searchfor"] = model.searchText; } catch { return View("Error", new ErrorViewModel { RequestId = "1" }); } return View("Index", model); }Analogamente al metodo di paging numerato, utilizziamo le impostazioni di ricerca Skip e Size per fare in modo che vengano restituiti solo i dati di cui abbiamo bisogno.
Aggiungere l'azione NextAsync al controller home. Si noti come restituisce un elenco, ogni hotel aggiunge due elementi all'elenco: un nome di hotel e una descrizione dell'hotel. Questo formato è impostato in modo che corrisponda all'uso della funzione scorrevole dei dati restituiti nella visualizzazione.
public async Task<ActionResult> NextAsync(SearchData model) { // Set the next page setting, and call the Index(model) action. model.paging = "next"; await Index(model).ConfigureAwait(false); // Create an empty list. var nextHotels = new List<string>(); // Add a hotel name, then description, to the list. await foreach (var searchResult in model.resultList.GetResultsAsync()) { nextHotels.Add(searchResult.Document.HotelName); nextHotels.Add(searchResult.Document.Description); } // Rather than return a view, return the list of data. return new JsonResult(nextHotels); }Se viene visualizzato un errore di sintassi su List<string>, aggiungere la seguente direttiva using all'inizio del file del controller.
using System.Collections.Generic;
Compilare ed eseguire il progetto
Ora seleziona Avvia senza eseguire debug (o premi il tasto F5).
Immettere un termine che fornirà un sacco di risultati (ad esempio "pool") e quindi testare la barra di scorrimento verticale. Attiva una nuova pagina di risultati?
Suggerimento
Per assicurarsi che nella prima pagina venga visualizzata una barra di scorrimento, la prima pagina dei risultati deve superare leggermente l'altezza dell'area in cui vengono visualizzate. Nell'esempio .box1 ha un'altezza di 30 pixel, .box2 ha un'altezza di 100 pixel e un margine inferiore di 24 pixel. Ogni voce usa quindi 154 pixel. Tre elementi occuperanno 3 x 154 = 462 pixel. Per assicurarsi che venga visualizzata una barra di scorrimento verticale, è necessario impostare un'altezza dell'area di visualizzazione inferiore a 462 pixel, anche 461 funziona. Questo problema si verifica solo nella prima pagina, dopo di che una barra di scorrimento apparirà sicuramente. La riga da aggiornare è: <div id="myDiv" style="width: 800px; altezza: 450px; overflow-y: scroll;" onscroll="scrolled()">.
Scorrere verso il basso fino alla fine dei risultati. Si noti che tutte le informazioni sono ora disponibili in una pagina di visualizzazione. È possibile scorrere fino alla parte superiore senza attivare alcuna chiamata al server.
Sistemi di scorrimento infiniti più sofisticati potrebbero usare la rotellina del mouse, o un altro meccanismo simile, per attivare il caricamento di una nuova pagina di risultati. In queste esercitazioni non verrà eseguito lo scorrimento infinito, ma ha un certo fascino perché evita clic aggiuntivi del mouse e si potrebbe voler analizzare altre opzioni.
Risultati
Si considerino le considerazioni seguenti di questo progetto:
- Il paging numerato è utile per le ricerche in cui l'ordine dei risultati è in qualche modo arbitrario, il che significa che potrebbe essere interessante per gli utenti nelle pagine successive.
- Lo scorrimento infinito è utile quando l'ordine dei risultati è particolarmente importante. Ad esempio, se i risultati vengono ordinati sulla distanza dal centro di una città di destinazione.
- Il paging numerato consente una navigazione migliore. Ad esempio, un utente può ricordare che un risultato interessante era nella pagina 6, mentre non esiste un riferimento così semplice nello scorrimento infinito.
- Lo scorrimento infinito ha un fascino di facilità, scorrendo in su e in giù senza numeri di pagina su cui fare clic.
- Una caratteristica chiave dello scorrimento infinito è che i risultati vengono accodati a una pagina esistente, non sostituendo quella pagina, che è efficiente.
- L'archiviazione temporanea persiste per una sola chiamata e deve essere reimpostata per sopravvivere a chiamate aggiuntive.
Passaggi successivi
Il paging è fondamentale per un'esperienza di ricerca. Con il paging ben coperto, il passaggio successivo consiste nel migliorare ulteriormente l'esperienza utente aggiungendo ricerche in tempo reale.