Paweł Łukasiewicz
2017-02-25
Paweł Łukasiewicz
2017-02-25
Udostępnij Udostępnij Kontakt
Wprowadzenie

Jeżeli rozpocząłeś swoją pracę z ASP.NET MVC prędzej czy poźniej spotkasz się z pojęciem Progresywanego Ulepszania oraz Dyskretnego JavaScript. Pomijając użyteczność i wydajność należy wspomnieć o bardzo ważnej korzyści – techniki te pozwalają na napisanie bardziej modułowego kodu JavaScript. W poniższym artykule skupię się wyłączenie na kontrolce autouzupełniania w aplikacji ASP.NET MVC

Rozpoczęcie pracy

W pierwszym kroku skupimy się na autouzupełnianiu w akcji – zaczniemy od wprowadzenia tekstu:

<label for=“somevalue“>Some value:</label><input type=“text“ id=“somevalue“ name=“somevalue“ />

Po dodaniu referencji do skryptu oraz pliku css dla jQuery UI możemy dopisać poniższy skrypt do naszego widoku:

$(document).ready(function () {
	$(“#somevalue“).autocomplete({
		source: “@Url.Action(“Autocomplete“)
	});
})
Powyższy skrypt identyfikuje wprowadzany tekst przez ID a następnie wywołuje funkcję autouzupełniania w celu podpięcia rezultatów do wskazanego elementu DOM (Document Object Model). Przekazujemy URL, aby zidentyfikować źródło danych. W tym celu przygotowałem prostą akcję, która jako wynik zwraca dane w postaci JSON. Zwróć uwagę, że na widoku skorzystałem z Url.Action do połączenia URL z akcją w tabeli routingów – należy unikać hard-codowania URL ponieważ prowadzi to do powstawania duplikatów w tabeli routingów oraz czyni proces zmiany routingów jeszcze trudniejszym.
public ActionResult Autocomplete(string term) 
{ 
    var items = new[] {"Audi", "Ferrari", "Porsche", "Audi Sport", "BMW"};
    var filteredItems = items.Where( 
        item => item.IndexOf(term, StringComparison.InvariantCultureIgnoreCase) >= 0 
        ); 
    return Json(filteredItems, JsonRequestBehavior.AllowGet); 
}
A tak przedstawia się efekt złożenia wszystkich powyższych komponetów:

Prezentacja działania metody Autocomplete

Czy jest więc coś złego w takim rozwiąznaiu? Hmm...takie rozwiązanie działa, ale zostało przygotowane w taki sposób, że nie nadaję się do użycia w innych projektach.

Dyskretne autouzupełnianie

Teraz, gdy mamy już działające autouzupełnianie , możemy przejść do kolejnego kroku. Przeniesiemy skrypt do osobnego pliku oraz wyczyścimy naszą stronę HTML (zmniejszymy jej rozmiar oraz poprawimy cache'owlaność skryptu). Wyzywanie, z którym się zmierzymy w tym kroku to wskazanie naszemu skryptowi z którym elementem ma współpracować oraz URL dla każdego elementu. Jeden ze sposobów to wskazanie, przy użyciu CSS, elementów, które potrzebują takiego zachowania a następnie taka modyfikacja skryptu, aby rozpoznawał te elementy przez klasę a nie identyfikator. To jednak nie rozwiąże problemu z URL (czyli lokalizacją naszej metody). Użyjemy podejścia opisanego w tym artykule: https://blogs.msdn.microsoft.com/stuartleeks/2012/04/23/asp-net-mvc-jquery-ui-autocomplete/

Istotą tego rozwiązania jest dołączenie dodatkowych elementów HTML a następnie utworzenie osobnych skryptów, które będą identyfikowały te elementy oraz implementowały odpowiednie zachowanie. Deklaratywne podejście jest powszechna techniką używana walidacji dostępną od ASP.NET MVC 3. Metodą dodawania dodatkowych informacji są atrybuty, które rozpoczynaja się od "data- i są ignorowane przez przeglądarkę. Dla autouzupełniania dodamy atrybut data-autocomplete-url, aby wskazać URL do pobierania danych autouzupełniania. Widok będzie teraz wyglądał następująco:

<label for=“somevalue“>Some value:</label><input type=“text“ id=“somevalue“ name=“somevalue“ data-autocomplete-url=“@Url.Action(“AutoComplete“)“ />
Następny krok to utworzenie osobnego pliku dla skryptu, który doda zachowanie autouzupełniania dla wskazanego elementu:
$(document).ready(function () { 
    $("*[data-autocomplete-url]") 
        .each(function () { 
            $(this).autocomplete({ 
                source: $(this).data("autocomplete-url") 
            }); 
        }); 
});
Skrypt ten identyfikuje wszystkie element z atrybutem data-autocomplete-url a następnie wywołuje autocomplete(), aby dodać widget uzupełniania jQuery dla każdego element stosując wartość atrybutu jako źródło danych autouzupełaniania.

Łączenie z modelem

Do tej pory skupialiśmy się na widokach w których ręcznie generowaliśmy element wejściowe. W ASP.NET MVC częściej korzystamy z tzw.: HTML Helpers (np. Html.TextBoxFor, Html.Editor.For), aby utworzyć elementy wejściowe, które powiązane sa z modelem. W tym scenariuszu, nasz widok (bez autouzupłeniania) może wyglądać następująco:

@Html.LabelFor(m=>m.SomeValue) 
@Html.TextBoxFor(m=>m.SomeValue)
Zwróć uwage, że pracuję z modelem, który ma właściwość SomeValue. Aby dodać autouzupełnianie musimy dodać nasz niestandardowy atrybut, który pozwoli wygenerować wejście. Na szczęście mamy przeciążanie dla TextBoxFor, które pozwalają nam na przekazanie dodatkowych atrybutów. Te dodatkowe atrybuty mogą być określone w instancji RouteValueDictionary albo (częściej) jako instancja obiektu. Właściwości instancji obiektu są używane do uzupełniania atrybutów (nazwa właściwości daje nazwę atrybutu, wartość właściwości określa wartość atrybutu). Jest jeszcze jedna dodatkowa sztuczka dodana do ASP.NET MVC 3: podkreślenia w nazwie właściwości są konwertowane do myślników w nazwach atrybutów. Innymi słowy, nazwa właściwości "data_autocomplete_url" zostaje zamieniona na "data-autocomplete-url" – jako nazwa atrybutu. Jest to szczególnie przydatne, ponieważ C# nie pozwala na myślniki w nazwach właściwości. Po przyswojeniu tej wiedzy wiedzy możemy wprowadzić modyfikacje w naszym widoku:
@Html.LabelFor(m=>m.SomeValue) 
@Html.TextBoxFor(m=>m.SomeValue, new { data_autocomplete_url = Url.Action("Autocomplete")})
Z tą zmianą skrypt doda zachowanie autouzupełniania za nas.

Html.AutocompleteFor

Możemy teraz uprościć ten zapis jeszcze bardziej poprzez utworzenie niestandardowego rozszerzenia HTML Helper: AutocompleteFor. Pomocniczny HTML to tak naprawdę wszystkie metody rozszerzające, a metoda AutocompleteFor jest dość prosta, gdyż musi jedynie otrzymać URL a następnie wskazać na okreslony TextBoxFor:

public static class AutocompleteHelpers 
{ 
    public static MvcHtmlString AutocompleteFor(this HtmlHelper html, Expression> expression, string actionName, string controllerName)
    {
        string autocompleteUrl = UrlHelper.GenerateUrl(null, actionName, controllerName,
                                                       null,
                                                       html.RouteCollection,
                                                       html.ViewContext.RequestContext,
                                                       includeImplicitMvcValues: true);
        return html.TextBoxFor(expression, new { data_autocomplete_url = autocompleteUrl});
    }
}

Z tak przygotowanym helperem widok przedstawia się w następujący sposób:
@Html.LabelFor(m=>m.SomeValue) 
@Html.AutocompleteFor(m=>m.SomeValue, "Autocomplete", "Home")

Podsumowanie

Mam nadzieję, że artykuł został napisany w sposób prosty i przejrzysty. Jeżeli w przyszłości znajdziesz skrypt napisany bezpośrednio na widoku, warto rozważyć, czy istnieją korzyści z wyodrębnienia go do osobnego pliku. Kiedy tylko skrypt będzie w oddzielnym pliku możesz przystąpić do pozbycia się zbędnego kodu lub połączenia z innymi skryptami.