niedziela, 9 września 2012

WinJS na żywo - odc.5 (m.in search, własny binding)

Tym razem z poziomu WinJS dokonałem integracji z systemową wyszukiwarką, sprawiając tym samym osiągnięcie przez aplikację WinJS tej samej funkcjonalności, którą wcześniej zrealizowałem w C#. Oczywiście wiązały się z tym też mniejsze zadania…

Kontrakt Search oraz stronę z wynikami wyszukiwań najszybciej dodamy korzystając z Visual Studio 2012. Z menu kontekstowego w oknie Solution Explorer wybieramy Add New Item, a następnie szablon Search Contract. Dokumentacja opisuje wszystkie potrzebne czynności oraz co zostaje dodawane do projektu aplikacji po wybraniu szablonu. Chwilę może zająć analiza kodu wygenerowanej strony. Parę rzeczy zwróciło w praktyce moją uwagę. Code-behind strony z wynikami wyszukiwań jest zadeklarowany w default.html, zamiast w HTML tej strony. Jest to podyktowane tym, że:

  • obsłużenie zdarzenia aktywacji aplikacji przez wyszukiwarkę
  • rejestracja na wpisanie zapytania w wyszukiwarce

muszą odbywać się globalnie przy uruchomieniu aplikacji i nie zdecydowano się na modyfikację pliku z kodem globalnym. Konkretnie chodzi o kod:

WinJS.Application.addEventListener("activated", function (args) {
        if (args.detail.kind === appModel.Activation.ActivationKind.search) {
            args.setPromise(ui.processAll().then(function () {
                if (!nav.location) {
                    nav.history.current = { location: Application.navigator.home, initialState: {} };
                }                   

                return nav.navigate(searchPageURI, { queryText: args.detail.queryText });
            }));
        }
    });

appModel.Search.SearchPane.getForCurrentView().onquerysubmitted = function (args) { nav.navigate(searchPageURI, args); };

Jednocześnie HTML strony z wynikami wyszukiwania nie odwołuje się do pliku JavaScript o takiej samej nazwie, co powoduje, że strona w designerze Blenda nie prezentuje się w prawidłowy sposób. Jeśli jednak otworzymy default.html i w trybie interaktywnym przenawigujemy do strony z wynikami, to prezentuje się ona wtedy prawidłowo. Stylowanie jest wtedy przyjemnością….

W wygenerowanej stronie są przykładowe dane, które potrzebujemy z reguły zmienić. W moim przypadku potrzebuję filtrować pobrane pliki wideo, które są zapisywane w folderze lokalnym aplikacji. W definicji strony powstał w związku z tym następujący kod:

ready: function (element, options) {
            …

      this._handleQuery(element, options);

      …

},

_handleQuery: function (element, args) {

           …
           this._searchData(element, this._queryText);

           …
},

_searchData: function (element, queryText) {
            if (window.Data) {
                var folder = Windows.Storage.ApplicationData.current.localFolder;
                var queryOptions = new Windows.Storage.Search.QueryOptions(Windows.Storage.Search.CommonFileQuery.defaultQuery, [".mp4"]);
                queryOptions.folderDepth = Windows.Storage.Search.FolderDepth.deep;
                queryOptions.indexerOption = Windows.Storage.Search.IndexerOption.useIndexerWhenAvailable;

                if (queryText !== "")
                    queryOptions.userSearchFilter = queryText;

                var fileQuery = folder.createFileQueryWithOptions(queryOptions);

                var dataSourceOptions = {
                    mode: Windows.Storage.FileProperties.ThumbnailMode.picturesView,
                    requestedThumbnailSize: 95,
                    thumbnailOptions: Windows.Storage.FileProperties.ThumbnailOptions.useCurrentScale
                };

                var dataSource = new WinJS.UI.StorageDataSource(fileQuery, dataSourceOptions);

                var listView = element.querySelector(".resultslist").winControl;
                listView.itemDataSource = dataSource;
            }
        }

By cieszyć się widokiem:

jstube_11

dokonanałem jeszcze trochę zmian w kodzie i stylach strony. Na łamach krótkiego postu nie wydają się one aż tak istotne, by je omawiać, z wyjątkiem kolorowania wyszukiwanej frazy na elementach ListView.

Wygenerowana strona otrzymuje już taką funkcjonalność w postaci zdefiniowanej funkcji konwertera  _markText. W czym więc problem? Pułapka tkwi w klasie StorageDataSource. Jak już ostatnio w swoich postach pisałem, wymaga ona bindingów onetime. Jak zdefiniować w WinJS binding onetime, który korzysta z konwertera? Nie jest to tak oczywiste jak… w XAML-u. Sprawę zakończyło zdefiniowanie nowego bindingu zawierającego w sobie także logikę konwertera:

_bindMarkedText: function (source, sourceProperty, destination, destinationProperty) {
            var element = destination;
            var text = source && source[sourceProperty];
            if (!text) return;
            if (this._lastSearch)
                destination[destinationProperty] = text.replace(this._lastSearch, "<mark>" + this._lastSearch + "</mark>");
            else
                destination[destinationProperty] = text;
        },

Większa powszechność tworzenia własnych bindingów i ich większa otwartość jest typowa także dla innych bibliotek JS (np. Knockout’a). W ich logice oprócz konwersji mogą znajdować się także operacje na elementach wizualnych, w tym animacje. Wracając do naszego przypadku chciałbym zwrócić uwagę na funkcję WinJS.Binding.initializer, której użyłem do zadeklarowania, że funkcja _bindMarkedText może być stosowana w deklaratywnym data bindingu.

_handleQuery: function (element, args) {
            …
            WinJS.Namespace.define("searchVideos", { bindMarkedText: WinJS.Binding.initializer(this._bindMarkedText.bind(this)) });            
            …

        },

Teraz wystarczy dopilnować, by w HTML tekst w szablonie listy był zbindowany tak:

<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
        <div class="item">

            …
            <div class="item-content">
                <h3 class="item-title win-type-x-small" data-win-bind="innerHTML: name searchVideos.bindMarkedText"></h3>               
            </div>
        </div>
    </div>

Na koniec zmieniłem sobie w CSS kolor zaznaczonego tekstu z domyślnego niebieskiego na pomarańczowy:

.searchVideos section[role=main] .resultslist .item .item-content mark {
                    background: transparent;
                    color: #D8512B;

     }

Do usłyszenia.

Brak komentarzy: