Bootstrap-datepicker i ASP.NET MVC

Bootstrap datepicker pojawił się w PizzaMVC przy okazji migracji z ASP.NET MVC 4 na 5. Ponieważ wersja 5 standarodowo korzysta z Bootstrapa, musiałem wymienić poprzednie kontrolki (pochodzące z jQuery UI) na coś kompatybilnego z Bootstrapem. Wtedy zrobiłem to pod presją czasu, wrzucając luźno pliki JS i podpinając je po linii najmniejszego oporu. Teraz przyszła pora na poprawki.

Co się stało?

To, że datepicker nie działa, zauważyłem przy okazji prac nad lokalizacją zasobów aplikacji. Po zmianie kultury np. na “en-GB” lub “en-US” kontrolka zaczęła wyświetlać dziwne daty oraz komunikat o braku wartości w polu. Poprawnie działała tylko dla kultury “pl-PL”. Ponadto popup z kalendarzem pojawiał się nad polem tekstowym zamiast pod nim, co sprawiało, że nie zawsze było widać cały kalendarz.

Top, bottom - who cares?

Jako pierwszy problem do rozwiązania wybrałem ten prostszy, czyli wyświetlanie popupa. Jak się okazało, nie tylko ja miałem z tym problem, było już kilka zgłoszeń na GitHubie projektu: 1, 2, 3

Najbardziej spodobało mi się wyjaśnienie z pierwszego linka:

“right bottom” should result in the picker showing upward and to the left from the element (the “tooth” that points to the element should be to the bottom-right of the picker).

Dwa dni później, gdy zebrałem się już z podłogi zszywając resztki przepony dratwą, zaktualizowałem nową wersję kontrolki z NuGeta. Zgodnie z tym, co obiecano tutaj teraz ustawianie orientacji działa. Można się zająć poważnymi sprawami.

Jeden format w backendzie i w kontrolce

Pierwotnie ustawiłem na sztywno format daty dla datepickera na zgodny z ISO8601, czyli yyyy-mm-dd. Oczywiście, zmiana kultury aplikacji na en-GB powoduje, że data jest w formacie dd/mm/yyyy. Bootstrap-datepicker jest taki sprytny, że zamiast rzucić jakimś błędem, liczy modulo z podanej wartości, więc np. 2016-12-01 przelicza na 01/12/01 i ustawia taką datę. Na szczęście bootstrap-datepicker zawiera spory zestaw ustawień regionalnych dla większości języków. Wystarczy mu tylko ustawić parametr language na odpowiedni kod kultury (który oczywiście biorę z Thread.CurrentThread.CurrentCulture.Name). Co więcej, ta kontrolka jest tak sprytna, że jeśli nie znajdzie kultury o pełnym kodzie, np. de-DE, to użyje de! Bardzo ułatwiło mi to życie, przetestowałem ustawienia dla popularnych ustawień (de, es, fr, en-GB, en-US) i wszystko wyświetlało się poprawnie.

Pusta wartość

Chciałem, aby w przypadku, gdy wartość pola viewmodelu jest nieustalona, czyli wynosi default(DateTime), kontrolka była pusta (a nie zawierała brzydkiego tekstu w rodzaju: “0001-01-01”). W tym celu dodałem mały hack:

1 if (dateInput.val() === "@emptyDateString") {
2     dateInput.datepicker("update", "");
3 }

gdzie emptyDateString to DateTime.MinValue.ToShortDateString().

Poprawka jQuery Validation

Jak wiadomo, ASP.NET MVC potrafi walidować viewmodele po stronie klienckiej. Korzysta w tym celu z biblioteki jQuery Validation. Niestety, jej domyślna implementacja nie obsługuje dat w formatach innych niż standardowy dla JavaScriptu. Na dowód implementacja:

1 date: function(value, element) {
2     return this.optional(element) || !/Invalid|NaN/.test(new Date(value));
3 },

Musiałem to poprawić. Początkowo chciałem użyć moment.js, ale że ta biblioteka jest, jaka jest, ostatecznie wybrałem coś, co ma sensowniejsze dla mnie API: Datejs. Wystarczyło tylko napisać kod podmieniający domyślną implementację walidatora:

1 $(function () {
2     $.validator.methods.date = function (value, element) {
3         return this.optional(element) || Date.parseExact(value, "@netDateFormatString") !== null;
4     }
5 });

To nie wszystko

Mogłoby się wydawać, że ustawianie kultury i walidacja to wszystko, co jest potrzebne do działania datepickera. Niestety, nie do końca. O ile dobrze działają ustawienia dla najpopularniejszych języków, o tyle np. polskie nie działają prawidłowo. Czyli właściwie doprowadziłem do sytuacji całkowicie odwrotnej niz początkowa, gdyż wtedy tylko “pl-PL” działała. :) Ale o tym, czemu tak się stało i jak to naprawić napiszę w innym poście.

Opublikowano: