ASP.NET MVC – okna modalne z wykorzystaniem Kendo UI

Ostatnimi czasy w pracy eksperymentujemy z ASP.NET MVC. Jednym z problemów na jakie natknąłem się poznając tą technologię było utworzenie okien modalnych. Po niezbyt owocnej walce z kontrolkami ze stajni Devexpressa postanowiliśmy, że skorzystamy z darmowej biblioteki Kendo UI napisanej w jQuery. Wspomnianą wcześniej bibliotekę możemy znaleźć pod adresem http://www.kendoui.com/get-kendo-ui.aspx. Aby uzyskać dostęp do plików należy zarejestrować się w systemie – oczywiście rejestracja jest darmowa. Po zassaniu wszystkich plików kopiujemy zawartość katalogu “source” do naszego projektu MVC. W moim przypadku utworzyłem katalog Kendo w katalogu Content i tam umieściłem zawartość katalogu “source”.
Structure

Mając wszystkie pliki w komplecie musimy wskazać naszej aplikacji aby zaczęła z nich korzystać. Dodajmy zatem w pliku _Layout.cshtml następujące elementy

Przejdźmy teraz do najważniejszej części, a mianowicie do tworzenia okna modalnego. Biblioteka Kendo UI udostępnia nam funkcję kendoWindow, dzięki której jesteśmy w stanie utworzyć okno modalne. Okno to jednak nie ma w sobie żadnej zawartości – nie posiada nawet przycisków typu “Zapisz” oraz “Anuluj”. Utworzenie i załadowanie zawartości okna oraz reakcja na kliknięcia w potencjalne przyciski musi zostać obsłużona przez nas. Zacznijmy zatem od utworzenia zawartości okna. Załóżmy, że okno, które za chwilę stworzymy będzie służyło do dodawania nowego produktu do bazy danych. Klasa produkt jest bardzo prostą klasą prezentującą się w następujący sposób:

Stwórzmy zatem strongly-typed partial view, który będzie zawartością naszego okna modalnego. Widok ten będzie posiadał dwa textboxy do wypełniania propertisów obiektu, oraz przyciski “Zapisz” oraz “Anuluj”. Zacznijmy od dodania odpowiednich funkcji w kontrolerze. Pierwszą z nich będzie akcja AddProduct();

W celu dodania widoku klikamy PPM na nazwę funkcji i wybieramy opcję “Add View”. W otwartym oknie zaznaczmy opcję “Create Strongly Typed View”, a w comboboxie wpisujemy “Product”. Ponadto zaznaczamy checkboxa “Create as a partial view”.
addcontroller
W katalogu Home pojawił się nowy plik AddProduct.cshtml. W pliku tym zdefiniujemy wygląd zawartości naszego okna modalnego. Jako, że nasze dane będą musiały zostać wysłane na serwer do zapisu, skorzystamy z funkcji pomocniczej Html.BeginForm, w której to umieścimy wszystkie potrzebne elementy do edycji produktu.

Dodajmy teraz na naszej stronie głównej linka, który wywoła nasze okno modalne.

Przyszedł teraz czas na najważniejszą część wpisu, a mianowicie na wykorzystaniu jQuery oraz kendoWindow do pokazania okna edycji/dodawania produktu. Najpierw podpinamy się do zdarzenia(dodajemy handler) click utworzonego przed chwilą odnośnika. W funkcji obsługującej zdarzenie click musimy utworzyć okno,wypełnić jego zawartość oraz pokazać okno.

Korzystając z selektora jQuery znajdujemy link opisany id-kiem “addProduct”, a następnie podłączamy się do zdarzenia click. W funkcji obsługującej zdarzenie musimy najpierw zapobiec wywołaniu się domyślnej akcji wykonującej się po naciśnięciu na link. Robimy to przy pomocy funkcji prefentDefault(). W kolejnych krokach tworzymy okno modalne zgodnie ze wskazówkami znajdującymi się w dokumentacji Kendo UI http://demos.kendoui.com/web/window/index.html. Najważniejszym elementem przy tworzeniu okna jest dynamiczne załadowanie jego zawartości poprzez podanie linka do akcji w kontrolerze. Linka tego jesteśmy w stanie wyciągnąć wykorzystując kolejną użyteczną funkcję jQuery – attr(‘nazwaAtrybutu’).
Od tej chwili po naciśnięciu odnośnika “Dodaj produkt” zawsze będzie odpalane okno modalne dodawania nowego produktu. Teraz musimy jeszcze obsłużyć zdarzenia click na przyciskach “Zapisz” oraz “Anuluj”. Do funkcji przedstawionej powyżej dopisujemy następujące linijki

Tutaj sytuacja odrobinę się komplikuje gdyż musimy wysłać wprowadzone dane do kontrolera oraz obsłużyć ewentualne błędy walidacji. Zaczynamy od znalezienia przycisku opisanego id-kiem “btnSave” oraz podpięcia się do zdarzenia click. Ponownie anulujemy domyślną akcję wywołując funkcję preventDefault(). W kolejnym kroku wyciągamy url-a, który miał zostać wywołany po naciśnięciu przycisku. Robimy to, ponownie wykorzystując funkcję attr(‘nazwaAtrybutu’). Jednakże teraz wyszukujemy tego atrybutu na obiekcie o id =”fmAddProduct” (taki id nadaliśmy “formie” na której są pola edycyjne produktu).Ostatnim elementem jest wykonanie asynchronicznego zapytania AJAX. Do tego zapytania przekazujemy wcześniej uzyskany url,określamy typ zapytania jako “POST” oraz przesyłamy dane wykorzystując funkcję serialize z biblioteki jQuery. Na samym końcu musimy sprecyzować co ma się stać w przypadku gdy wszystko pójdzie ok – parametr success, a co w przypadku gdy coś będzie nie tak – parametr error. Musimy oczywiście dodać do kontrolera jeszcze jedną akcję. Tym razem będzie to również akcja AddProduct, jednakże będzie to akcja HttpPost posiadająca jeden parametr typu Product

okna modalne
Ostatnią rzeczą jaką musimy zrobić jest wyłączenie cachowania widoków, które są pokazywane w oknie modalnym – można to zrobić wykorzystując atrybut OutputCaching. W przypadku gdy nie wyłączymy cachowania, okna modalne, które raz nie przeszły walidacji nie będą mogły zostać zapisane. W celu wyłączenia cachowania dla poszczególnych widoków najpierw musimy stworzyć profil cachowania. Dodajemy taki oto wpis do pliku Web.config

a następnie dekorujemy atrybutem

akcje AddProduct() oraz AddProduct(Product product)
To by było na tyle, projekt można zassać z tego linka http://www.4shared.com/rar/F4pNi3VH/ModalDialogs.html

ASP.NET MVC – okna modalne z wykorzystaniem Kendo UI

MVVM i okna modalne

Często podczas użytkowania programów zachodzi konieczność pokazania dodatkowego okna, służącego do interakcji z użytkownikiem. Najczęściej okno takie należy pokazać w określonym przypadku. Jak już wcześniej zostało wspomniane cała logika powinna być umieszczona w ViewMoedlu, zatem nasuwa się pytanie, w jaki sposób z ViewModelu pokazać okno modalne – przecież ViewModel nie powinien mieć żadnych informacji o widoku. Rozwiązanie tego problemu polega na oddelegowaniu pokazywania widoków lub MessageBox-ów do wyspecjalizowanych klas tzw. serwisów. Ponadto w przypadku gdy chcemy pokazywać proste komunikaty możemy również skorzystać z Interaction/Interactivity oraz klasy Prisma InteractionRequest.

1. InteractionRequest

Jak już wcześniej wspomniano klasa InteractionRequest odpowiedzialna jest za pokazywanie komunikatów użytkownikowi – koordynuje ona działanie pomiędzy ViewModelem a widokiem. W celu skorzystania z klasy InteractionRequest w ViewModelu tworzymy publiczną właściwość udostpęniającą obiekt typu InteractionRequest na zewnątrz

Jak widać za generyczny typ T została podstawiona klasa Notification. Parametr T może przyjmować klasy dziedziczące po klasie Notification. Domyślnie w Prismie są dwie takie klasy:

  • Notification – wspomniana już wcześniej, służy do powiadomienia użytkownika o jakimś zdarzeniu
  • Confirmation – również służy powiadominiu użytkownika o jakiś zdarzeniu, ale zwraca ona reakcję użytkownika (czy potwierdził daną akcję czy nie – coś jak MessageBox YesNo)

W celu wywołania okienka należy odpalić funkcję Raise na przykład w taki sposób:

Funkcja Raise przyjmuje jeden lub dwa parametry. Przedstawy bardziej zaawansowaną wersję tej funkcji – tą z dwoma parametrami. W pierwszym parametrze podajemy obiekt typu Notification, w którym przekazujemy informacje do przekazania użytkownikowi (Content) oraz tytuł okna (Title). Drugim parametrem jest delegat Action , który określa co mamy zrobić po zamknięciu okna przez użytkownika.
Ostatnią rzeczą jaką musimy zrobić, żeby wykorzystać NotificationRequest jest zdefiniowanie triggera w widoku, który będzie reagował na odpalenie funkcji Raise. Robimy to w następujący sposób:

  • definiujemy w XAML-u alias do namespaca interactivity
  • tworzymy InteractionRequestTrigger

2. MessageBoxService

Alternatywą dla InteractionRequest jest stworzenie własnej klasy, do której zostałyby oddelegowane wszystkie prośby o pokazania MessageBoxa. Przykładowa implementacja takiego serwisu może wyglądać w następujący sposób. Po pierwsze stwórzmy interfejs z funkcjami potrzebnymi do pokazania MessageBoxa

Następnie zaimplementujmy ten interfejs w klasie MessageBoxService – właśnie do tej klasy będą oddelegowywane wszystkie prośby o pokazanie MessageBoxa

Taki serwis musimy następnie przekazać do każdego ViewModelu , w którym będzie potrzeba pokazania MessageBoxa. Jako, że ręczne przekazywanie tego obiektu może być nużące, możemy skorzystać z dobrodziejstw Inversion of Control. Ja skorzystałem z kontenera z Unity. Po pierwsze musimy zarejestrować nasz typ w kontenerze

następnie MessageBoxService może zostać wstrzyknięty do każdego ViewModelu przy np. przy pomocy wstrzykiwania właściwości.

Oczywiście żeby nasza właściwość została wstrzyknięta, nasz ViewModel musi zostać stworzony przy użycia kontenera IO
Zauważmy, że właściwość MessageBoxService nie jest konkretnym typem, lecz interfejsem. Dzięki takiemu podejściu nasza aplikacja wciąż jest w łatwy sposób testowalna oraz ViewModel nie ma nic wspólnego z klasami widoku.

3. ModalWindowService

Do pokazywania widoków(jako okien) z poziomu ViewModelu posłużmy się podobnym mechanizmem jak w przypadku pokazywania MessageBox-ów. Zasada działania klasy, która stworzymy będzie taka sama, jednakże będziemy musieli odrobinę poszerzyć jej funkcjonalność. Po pierwsze tak jak poprzednio stwórzmy odpowiedni interfejs – IModalWindowService.
UWAGA
Przykład ModalWindowServicu zostanie zaprezentowany dla Silverlight-a, gdyż okna modalne w silerlighcie to tak naprawdę okna semi-modalne. Główny wątek się nie zatrzymuje, jednakże użytkownik nie ma możliwości interakcji z oknami będącymi pod aktualnie widocznym oknem.

Zauważmy, że funkcja ShowDialog w parametrze nie przyjmuje konkretnego widoku, lecz interfejs, który każde okno modalne powinno implementować – IModalView. Interfejs ten może wyglądać w następujący sposób:

Jako, że okna modalne w Silverlighcie są tak naprawdę oknami semi-modalnymi (patrz wytłumaczenie w sekcji UWAGA) musimy wiedzieć kiedy dane okno zostanie zamknięte. Dlatego, też interfejs ten zawiera zdarzenie

ponadto wypadałoby sprawdzić czy dane wpisane w oknie są poprawne, dlatego też dodano zdarzenie

w przypadku gdy dane wpisane w oknie nie będą poprawne, nasz ModalDialogService zapobiegnie zamknięciu okna. Ostatecznie nasza klasa ModalDialogService może wyglądać w następujący sposób:

Nasz serwis podobnie jak w przypadku MessageBoxServicu rejestrujemy w kontenerze, a następnie wstrzykujemy go do ViewModelu

Pozostaje nam jedynie kwestia wyjaśnienia w jaki sposób wywołać nasze okno. Nie powinniśmy tworzyć okna w ViewModelu, gdyż łamie to zasadę MVVM-a. Dlatego też po raz kolejny skorzystamy z kontenera IoC. W kontenerze IoC rejestrujemy sobie nasz widok

jako, że ViewModel jest wstrzykiwany do View poprzez Dependency Injection należy również w kontenerze zarejestrować odpowiedni typ ViewModelu – czyli w tym przypadku AddPersonViewModel

Mając już zarejestrowane w kontenerze wszystkie niezbędne typu możemy wywołać okno z ViewModelu w następujący sposób:

Obiekt widoku otrzymujemy z kontenera IoC wykorzystując funkcję Resolve, w miejsce LambdaExpression wstawiamy nasze wyrażenie w którym określamy co zrobimy po poprawnym zamknięciu okna. Zauważmy, że ViewModel nie ma żadnych informacji o widoku – kontener nie zwraca nam konkretnego typu lecz interfejs. Dzięki takiemu podejściu nasza aplikacja może być w łatwy sposób testowalna.

MVVM i okna modalne