czwartek, 6 września 2012

WinJS na żywo - odc.4 (m.in Flyout, progress, stylowanie, binding, transfer w tle)

Tym razem padło na wizualizację dla pobieranych plików. Wzorowałem się na wcześniej powstałej wersji w C#, ale niektóre zagadnienia rozwiązałem inaczej i nieco przestylowałem wygląd… Jak zwykle Blend okazał się bardzo pomocny, a tryb interaktywny nieoceniony… Pewnej przebudowy wymagała też implementacja pobierania plików, tak że obecnie dostarcza listę bindowalnych obiektów z informacjami o transferach… Ale po kolei.

Jak zrobić coś ala-dropdownbutton w aplikacji Windows 8? Najbardziej odpowiednia wydaje się do tego kontrolka Flyout. W jej wnętrzu umieściłem pole tekstowe z nagłówkiem oraz ListView z layoutem listy. HTML wygląda następująco:

  <div id="statusFlyout" data-win-control="WinJS.UI.Flyout">
        <div>
            <div id="progressheader">
                <span>Downloading&nbsp;</span><span id="downloadscount">3</span><span>&nbsp;files</span>
            </div>           
            <div id="progresslist" data-win-control="WinJS.UI.ListView"
                data-win-options="{layout:{type:WinJS.UI.ListLayout}, selectionMode:'none', tapBehavior:'none'}">
            </div>
        </div>
    </div>  

Kontrolka Flyout wraz z zawartością nie jest domyślnie widoczna. Chcemy pokazywać ją pod przyciskiem statusButton wyświetlającym informacje o ilości pobieranych plików, który ma także powodować jej wyświetlenie. Do tego przycisku podpinamy więc handler:

function showStatusFlyout() {
        var statusButton = document.getElementById("statusButton");    
        document.getElementById("statusFlyout").winControl.show(statusButton, "bottom", "right");     
    }

W tym przypadku metodzie show przekazałem:

  • obiekt, do którego chcemy się doczepić (statusButton)
  • krawędź doczepienia (bottom)
  • wyrównanie (right - do prawej krawędzi statusButton)

Dotknięcie każdego punktu poza obszarem popup’a spowoduje jego zamknięcie.

ListView umieszczony we Flyout zachowuje się nieco inaczej. Jeśli ustawimy źródło danych na ListView, w momencie, gdy nie jest widoczne, to po otwarciu Flyout… lista będzie pusta. Podyktowane jest to pewną optymalizacją, która zaszła w wersji finalnej - niewidoczne kontrolki nie podlegają przetwarzaniu. Możemy jednak przed pokazaniem ListView wymusić przetworzenie jego layoutu:

var statusFlyout = element.querySelector("#statusFlyout");
            statusFlyout.addEventListener("beforeshow", function (event) {               
                var progresslist = element.querySelector("#progresslist").winControl;               
                progresslist.forceLayout();               
            });

Teraz trochę o stylowaniu.

jstube_9

HTML szablonu elementu listy wizualizującej transfery plików wygląda następująco:

<div class="progressitemtemplate" data-win-control="WinJS.Binding.Template">
        <div class="progressitem">          
            <h4 class="progressitem-title" data-win-bind="textContent: fileName"></h4>               
            <progress class="progressitem-progress" max="100" data-win-bind="value:progress"></progress>
            <div class="progressitem-details">               
                <span data-win-bind="textContent: bytesReceived"></span>
                <span class="text-part">of</span>
                <span class="text-part" data-win-bind="textContent: totalBytesToReceive"></span>
                <span class="text-part">KB</span>
            </div>
            <button class="progressitem-cancelbutton" type="button"></button>          
        </div>
    </div>

Nie sposób w tym dość krótkim poście przedstawić wszystkie szczegóły związane ze stylowaniem, zwrócę jedynie uwagę na pewne aspekty związane z ListView oraz na kontrolkę <progress>.

Aby ustawić przezroczyste tło na ListView, wyłączyć standardową wizualizację na najeżdżanie,  zrobić odpowiedni margines wystarczy:

#progresslist .win-container:not(.footprint):not(.hover)
{
    background-color: transparent;
}

#progresslist .win-container:hover {
    outline: transparent solid 0px;
}

#progresslist > .win-vertical .win-container {
    margin: 5px;
}

Przy okazji mała dygresja odnośnie animacji w WinJS. Jakby komuś przyszło do głowy wyłączyć standardowo wbudowane animacje w ListView, to okaże się, że przełącznika w CSS do tego… nie ma. Jest natomiast możliwość globalnego wyłączenia/włączenia efektów animacyjnych dla całej aplikacji (to trochę bardziej złożone zagadnienie, odsyłam do opisów metod WinJS.UI.enableAnimations() i WinJS.UI.disableAnimations() w MSDN). Tutaj nie wyłączałem animacji…

Kontrolka <progress> jest standardowa dla HTML5, tutaj ma domyślny wygląd w stylu Modern UI/Metro. Styl CSS dla niej przyjął u mnie ostatecznie postać:

#progresslist .progressitem .progressitem-progress {
    width: 100%;
    color: #D8512B;
    background-color: #3D3D3D;
    -ms-grid-row: 2;
    margin-top: 10px;
    margin-bottom: 10px;
    margin-left: 1px;
}

Co potrzebujemy jeszcze zrobić, by cieszyć się widokiem, takim jak poniżej?

jstube_10

Z pewnością potrzebujemy powiązać informacje o transferach z UI. Możemy w WinJS.Binding.List trzymać bindowalne obiekty, które możemy sobie budować na podstawie obiektów DownloadOperation:

function makeListEntryFromDownload(download, existing) {
       
        var entry = {};       
      
        entry.guid = download.guid;
        entry.fileName = download.resultFile.name.replace(".mp4","");
        entry.bytesReceived = download.progress.bytesReceived;
        entry.totalBytesToReceive = download.progress.totalBytesToReceive;
        entry.progress = (entry.bytesReceived / entry.totalBytesToReceive) * 100.0;
       
        if (existing) {
            entry.promise = download.attachAsync();
        }
        else {           
            entry.promise = download.startAsync();
        }

        entry.promise = entry.promise.then(
            function () {
                downloadComplete(entry, download);
            },
            function (e) {
                downloadError(entry, e);
            },
            function () {
                downloadProgress(entry, download);
            }
        );
       
        entry.cancelDownload = function (e) {
            entry.promise.cancel();
        }

        return (entry);
    }

Metoda WinJS.Binding.as(data) robi z podanego obiektu bindowalny obiekt. 

Przedstawiony wyżej kod wykorzystujemy dla obiektów DownloadOperation aktualnie zakolejkowanych transferów w serwisie w następujący sposób:

var entry = makeListEntryFromDownload(download, true);
bindingList.push(WinJS.Binding.as(entry));

BindingList to WinJS.Binding.List, którą możemy powiązać z ListView. W przypadku zlecenia nowego transferu napisaną przez nas metodą wywołujemy z parametrem false zamiast true.

Bindowalne obiekty możemy uaktualniać w następujący sposób:

function downloadProgress(entry, download) {
        var bound = WinJS.Binding.as(entry);
        bound.bytesReceived = download.progress.bytesReceived;
        bound.totalBytesToReceive = download.progress.totalBytesToReceive;
        bound.progress = (bound.bytesReceived / bound.totalBytesToReceive) * 100.0;
    }

Całość kodu zorganizowałem sobie podobnie jak w przykładzie Mike Taulty'ego, który ostatnio przypadł mi do gustu. Polecam zapoznać się z nim.

Przy okazji mała dygresja odnośnie transferu plików w tle. W trakcie rozwijania Windows 8, ostatecznie stanęło na tym, że transfery plików są kontynuowane przy uśpionej aplikacji. Jeśli proces aplikacji po jej uśpieniu zostanie zakończony, to przy ponownym uruchomieniu aplikacji, transfery plików są restartowane (zapamiętywana jest lista plików do transferu).

Stay tuned!

Brak komentarzy: