Globalizacji ciąg dalszy

Kilka dni temu ogłosiłem nadzieję na zakończenie moich zmagań z Bootstrap Datepicker. Jak wiadomo, nadzieja jest matką głupich, i dzisiaj musiałem wrócić do tego tematu przy okazji naprawiania dat w gridzie. Po ostatnich zmianach zauważyłem, że daty w kolumnach wyświetlane są w amerykańskim formacie, zaś wyszukiwanie nie działa.

Wyszukiwanie

Oryginalny jqGrid korzystał z filtrowania dat przy użyciu kontrolki jQuery UI. Po przejściu na Bootstrapa i wprowadzeniu kompatybilnego z nim Datepickera, pojawił się on również w gridzie. Nic dziwnego, wszakże po załączeniu bootstrap-datepicker.js na stronie, funkcja datepicker konfiguruje teraz kontrolkę Bootstrapa, a nie jQuery.

Początkowo chciałem zaktualizować kod biblioteki MvcJqGrid, której używam do generowania kodu grida o obsługę innej kontrolki. Niestety kod tej biblioteki nie jest zbyt elastyczny i moim zdaniem wymaga większej refaktoryzacji. Nie chcę się tym teraz zajmować, więc wybrałem prostsze rozwiązanie. Dość toporne, ale za to skuteczne. Utworzyłem klasę JqGridConfigurationUpdater, której zadaniem jest poprawienie kodu JavaScript wygenerowanego przez MvcJqGrid poprzez zamianę takiego ciągu znaków:

1 .datepicker({{changeYear:true, onSelect: function() {{var sgrid = $('#{0}')[0]; sgrid.triggerToolbar();}},dateFormat:'dd-mm-yy'}});}}

Na taki:

1 .datepicker({{language: '{0}', clearBtn: true, orientation: 'bottom', autoclose: true}}).on('hide', function(e){{var sgrid = $('#{1}')[0]; sgrid.triggerToolbar();}});}}

Przy czym {0} to kod kultury, a {1} to id grida. Proste i nie wymagające przesadnego nakładu pracy.

Mały bonus tego rozwiązania

Przy okazji ta klasa zajmuje się teraz naprawianiem pozostałych problemów z gridem, a są to:

  1. Lokalizacja tekstu “Wyczyść filtry”.
  2. Brakująca opcja “Wszystkie” w dropdownach służących do filtrowania kolumn zawierających enumy.
  3. Wymuszenie tematu Bootstrapa zamiast jQuery UI.

Wyświetlanie

Przez jakiś czas debugowałem kod JavaScript, próowałem konfigurować grida za pomocą róznych formatterów, ale nie przyniosło mi to żadnego efektu. Zacząłem się zastanawiać czy czasem nie wysyłam zepsutych dat z serwera. I okazało się to prawdą. Już chciałem winić którąś z niewinnych JavaScriptowych biblioteczek, a wina była w 100% moja. Na dodatek to był piękny WTF.

Ponieważ jqGrid wymaga danych w formacie JSON:

 1 {
 2   "total": "xxx",
 3   "page": "yyy",
 4   "records": "zzz",
 5   "rows" : [
 6     {"id" :"1", "cell" :["cell11", "cell12", "cell13"]},
 7     {"id" :"2", "cell":["cell21", "cell22", "cell23"]},
 8       ...
 9   ]
10 }

Czego odpowiednikiem w .NET jest JsonResult utworzony na podstawie anonimowego obiektu o określonej strukturze:

1 var jsonData = new
2 {
3     total = listResult.PagingInfo.TotalPages,
4     page = gridSettings.PageIndex,
5     records = listResult.PagingInfo.TotalItemsCount,
6     rows = items
7 };

Główny problem jest z generycznym utworzeniem kolekcji items. Z backendu otrzymuję przecież kolekcję generycznego typu TGridModel, natomiast do jsonData muszę wstawić anonimowe obiekty. Konwersją zajmuje się klasa TypeToJqGridObjectMapperGenerator. Efektem jej działania jest coś takiego:

1 Func<T, object> func = c => new
2 {
3     id = c.Id,
4     cell = new[] {
5        c.SomeStringProperty, Convert.ToString(c.SomeDate, polishCulture), EnumHelper.GetDisplayName(typeof(SomeEnum), c.SomeEnum)
6     }
7 };

Jak widać, kluczowa jest tutaj zapewnienie, aby wszystkie wartości zostały skonwertowane do typu string.

I wewnątrz tej klasy miałem taki piękny kod:

1 polishCultureExpression = Expression.Constant(new CultureInfo("en-US"));
2 ...
3 // Calling Convert.ToString(property type, IFormatProvider)
4 var methodParmetersTypes = new[] { propertyType, typeof(IFormatProvider) };
5 MethodInfo convertToStringMethod = typeof(Convert).GetMethod("ToString", methodParmetersTypes);
6 propertyExpression = Expression.Call(convertToStringMethod, propertyExpression, polishCultureExpression);

Tak to jest, jak się nie zmienia nazw zmiennych na bieżąco, i nie globalizuje od początku. Poprawiłem to na:

1 // Calling Convert.ToString(property type, IFormatProvider)
2 var methodParmetersTypes = new[] { propertyType, typeof(IFormatProvider) };
3 MethodInfo convertToStringMethod = typeof(Convert).GetMethod("ToString", methodParmetersTypes);
4 
5 var cultureInfo = CultureInfoHelper.GetCultureInfoForType(propertyType);
6 var cultureExpression = Expression.Constant(cultureInfo);
7 
8 propertyExpression = Expression.Call(convertToStringMethod, propertyExpression, cultureExpression);

I teraz działa pięknie.

Morał

Nauczyłem się jednej rzeczy - choć brzmi to nieprawdopodobnie, nie zawsze winny jest JavaScript. Ale zazwyczaj tak, więc i tak szukanie jakichkolwiek błędów z wyświetlaniem danych, należy zaczynać od niego.

Opublikowano: