Jak za jeden týden připravit kompletní aplikaci pro Windows 8 s použitím technologií HTML5, CSS3 a JavaScript – den 1

MyWindows.cz

home archív foto video win8dev kontakt

Jak za jeden týden připravit kompletní aplikaci pro Windows 8 s použitím technologií HTML5, CSS3 a JavaScript – den 1

Den 0 byl věnován vytvoření domovské stránky a nastavení připojení k datům. Dnes se zaměříme na vytvoření zbývajících obrazovek a přidání podpory pro práci offline.

Drátěný model úplné aplikace vypadá takto:

Kompletní řešení pro "den 1" naleznete zde - http://www.catuhe.com/msdn/urza/day1.zip.

Celý projekt byl následně aktualizován pro Release Preview verzi Windows 8 (z Customer Preview), pro kompletní seriál (dny 0 - 4, bude popsáno v dalších článcích), je k dispozici zde: http://mywindows.cz/media/urza/UrzaGatherer.zip

Obrazovka rozšíření

Obrazovka rozšíření je postavena na ovládacím prvku ListView, pomocí něhož jsou zobrazeny všechny karty, které patří do daného rozšíření.

První řádek předkládá uživateli filtry a druhý řádek je vyplněn ovládacím prvkem ListView (cardsList):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>UrzaGatherer</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.0.6/css/ui-light.css" rel="stylesheet">
    <script src="//Microsoft.WinJS.0.6/js/base.js"></script>
    <script src="//Microsoft.WinJS.0.6/js/ui.js"></script>
    <!-- UrzaGatherer references -->
    <link href="expansion.css" rel="stylesheet">
    <script src="expansion.js"></script>
</head>
<body>
    <!--Templates-->
    <div class="itemTemplate" data-win-control="WinJS.Binding.Template">
        <div class="item-image-container" data-win-control="UrzaGatherer.Tools.DelayImageLoader" 
		data-win-options="{root: 'cards'}">
            <img class="item-image" data-win-bind="src: logo; alt: name" src="#" />
        </div>
        <div class="item-overlay">
            <h4 class="item-title" data-win-bind="textContent: name"></h4>
        </div>
    </div>
    <!--Content-->
    <div class="expansion fragment">
        <header aria-label="Header content" role="banner">
            <button class="win-backbutton" aria-label="Back"></button>
            <h1 class="titlearea win-type-ellipsis"><span class="pagetitle" id="pageTitle"></span>
            </h1>
        </header>
        <section aria-label="Main content" role="main">
            <div class="filters">
                <select id="orderSelect">
                    <option>By number</option>
                    <option>By name</option>
                </select>
                <select id="colorFilter">
                </select>
                <select id="authorFilter">
                </select>
                <select id="rarityFilter">
                </select>
                <select id="typeFilter">
                </select>
                <select id="checkFilter">
                    <option>All</option>
                    <option>Only missing</option>
                    <option>All except missing</option>
                </select>
                <input type="search" id="textFilter" />
            </div>
            <div class="cardsList" aria-label="List of cards" data-win-control="WinJS.UI.ListView"
                data-win-options="{itemTemplate:select('.itemTemplate'), selectionMode:'none', 
		swipeBehavior:'none', tapBehavior:'invoke',  layout:{type:WinJS.UI.GridLayout}}">
            </div>
        </section>
    </div>
</body>
</html>

Tak jako vždy, kostru stránky tvoří soubor HTML a soubor CSS jí dodává styl a definuje pozice všech značek. Rozložení filtrů je například definováno s použitím mřížky ze specifikace CSS3:

.expansion .filters {
	margin-left: 120px;
	-ms-grid-row: 1;
	-ms-grid-columns: auto auto auto auto auto auto 1fr;
	display: -ms-grid;
}
.expansion .filters #orderSelect {
	-ms-grid-column: 1;
}
.expansion .filters #colorFilter {
	-ms-grid-column: 2;
	margin-left: 10px;
}
.expansion .filters #authorFilter {
	-ms-grid-column: 3;
	margin-left: 10px;
}
.expansion .filters #rarityFilter {
	-ms-grid-column: 4;
	margin-left: 10px;
}
.expansion .filters #typeFilter {
	-ms-grid-column: 5;
	margin-left: 10px;
}
.expansion .filters #checkFilter {
	-ms-grid-column: 6;
	margin-left: 10px;
}
.expansion .filters #textFilter {
	-ms-grid-column: 7;
	-ms-grid-column-align: end;
	margin-right: 10px;
}

Podpora různých úrovní přiblížení

Aplikace UrzaGatherer musí umět zobrazovat karty v různé velikosti:

Velikost položek zatím určuje třída CSS:

.expansion .cardsList .win-item {
    height: 340px;
    width: 240px;
    color: white;
    background-color: white;
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr;
    display: -ms-grid;
    outline: rgba(0, 0, 0, 0.8) solid 2px;
}

Chcete-li změnit velikost položek, stačí tuto třídu aktualizovat. To provedete tak, že najdete soubor CSS v modelu DOM, vyhledáte příslušné pravidlo CSS (.expansion .cardsList .win-item) a aktualizujete pravidla pro šířku a výšku:

var updateSize = function (forceUpdate) {
    var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];

    if (!zoomLevel)
        return;

    var css = UrzaGatherer.Tools.FindCSS("expansion.css");
    var level = zoomLevel / 100.0;

    for (var index = 0; index < css.cssRules.length; index++) {
        if (css.cssRules[index].selectorText == ".expansion .cardsList .win-item") {
            css.cssRules[index].style.width = (480 * level) + "px";
            css.cssRules[index].style.height = (680 * level) + "px";
        }
    }

    if (forceUpdate) {
        var listView = document.querySelector(".cardsList").winControl;
        listView.forceLayout();
    }
}

Aktuální úroveň přiblížení je nastavení, které je součástí roamingu aplikace (http://msdn.microsoft.com/en-us/library/windows/apps/hh465094.aspx). K jeho načtení slouží výraz Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"].

Pro vyhledání správné šablony stylů CSS slouží následující kód:

var findCSS = function (name) {
    for (var index = 0; index < document.styleSheets.length; index++) {
        if (document.styleSheets[index].href.indexOf(name) != -1)
            return document.styleSheets[index];
    }
}

WinJS.Namespace.define("UrzaGatherer.Tools", {
    FindCSS: findCSS
});

Systém Windows volá funkci updateSize při každém volání speciální funkce:

Windows.Storage.ApplicationData.current.signalDataChanged();

Přidání nastavení aplikace

Nejvhodnějším místem pro konfiguraci úrovně přiblížení je přirozeně podokno nastavení. Když do své aplikace potřebujete přidat globální nastavení, rozhodně neuděláte chybu, když ho umístíte do podokna nastavení (http://msdn.microsoft.com/en-us/library/windows/apps/Hh780611.aspx):

Pokud chcete do své aplikace přidat podokno nastavení, je nutné definovat strukturu HTML v souboru HTML (já jsem použil soubor default.html):

<!--Settings-->
<div id="settingsDiv" data-win-control="WinJS.UI.SettingsFlyout" data-win-options="{width:'narrow'}">
    <div class="win-header">
        <button type="button" onclick="WinJS.UI.SettingsFlyout.show()" class="win-backbutton">
        </button>
        <div class="win-label">Settings</div>
    </div>
    <div class="win-content">
        <h4>Cards zoom level:</h4>
        <input type="range" id="zoomRange" min="20" max="80" value="50" />
    </div>
</div>

Jak vidíte, použil jsem úzký ovládací prvek WinJS.UI.SettingsFlyout s malou šířkou (nepotřebuji příliš místa).

Do souboru default.js stačí přidat naslouchací proces pro událost změny ovládacího prvku rozsahu (zoomRange):

// Zoom range
var zoomRange = document.getElementById("zoomRange");

var zoomLevel = Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"];
if (zoomLevel)
    zoomRange.value = zoomLevel;

zoomRange.addEventListener("change", function () {
    Windows.Storage.ApplicationData.current.roamingSettings.values["zoomLevel"] = zoomRange.value;
    Windows.Storage.ApplicationData.current.signalDataChanged();
});

Při každé změně rozsahu se aktualizuje nastavení v rámci roamingu a vyšle se signál pomocí funkce signalDataChanged.

Režim offline

Každá karta má velikost přibližně 150 kB. Proto potřebujete možnost uložit stažený soubor v místním počítači, abyste se pak nemuseli spoléhat na síť.

Aby to bylo možné, je nutné změnit způsob odkazování na obrázky. Místo odkazů na prostředky HTTP pomocí adresy URL (například http://www.mysite.com/card.jpg) se můžete odkazovat na místní prostředky uvnitř místní složky pomocí následujícího monikeru: ms-appdata:///local/cards/card.jpg. V případě, že dojde k chybě (například pokud nebude soubor nalezen), můžete obrázek stáhnout a uložit ho do místní složky.

Protože jste obrázky zobrazovali pomocí třídy DelayImageLoader (viz předchozí článek), můžete chování snadno změnit a přidat podporu práce offline:

var delayImageLoader = WinJS.Class.define(
        function (element, options) {
            this._element = element || document.createElement("div");
            this.element.winControl = this;

            var downloadImage = function (source, filename, img) {
                var url = options ? UrzaGatherer.Root + "/" + options.root + "/" + source : UrzaGatherer.Root + "/" + source;
                var xmlRequest = new XMLHttpRequest();
                xmlRequest.open("GET", url, true);
                xmlRequest.responseType = "blob";
                xmlRequest.onreadystatechange = function () {
                    if (xmlRequest.readyState === 4) {
                        UrzaGatherer.Stats.DecrementDownloads();
                        if (xmlRequest.status == 200) {
                            var blob = xmlRequest.response;
                            var input = blob.msDetachStream();

                            Windows.Storage.ApplicationData.current.localFolder.createFileAsync(filename,
                                Windows.Storage.CreationCollisionOption.replaceExisting).then( function (file) {
                                file.openAsync(Windows.Storage.FileAccessMode.readWrite).then( function (output) {
                                    Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then( function () {
                                        output.flushAsync().then(function () {
                                            input.close();
                                            output.close();
                                            img.src = "ms-appdata:///local/" + source;
                                        });
                                    });
                                });
                            });
                        }
                    }
                };
                xmlRequest.send(null);
            }

            WinJS.Utilities.addClass(this.element, "imageLoader");

            WinJS.Utilities.query("img", element).forEach(function (img) {
                img.addEventListener("load", function () {
                    WinJS.Utilities.addClass(img, "loaded");
                });
                img.addEventListener("error", function (err) {
                    if (img.src.substring(0, 5) == "http:") {
                    }
                    else {
                        var source = img.src.replace("ms-appdata:///local/", "");
                        var filename = source.replace("/", "\\");

                        Windows.Storage.ApplicationData.current.localFolder.getFileAsync(filename).then( 
                        	function (file) {
                            	var url = options ? UrzaGatherer.Root + "/" + options.root + "/" 
+ source : UrzaGatherer.Root + "/" + source; img.src = url; }, function () { // Not found UrzaGatherer.Stats.IncrementDownloads(); downloadImage(source, filename, img); }); } }); }); }, { element: { get: function () { return this._element; } }, }); WinJS.Namespace.define("UrzaGatherer.Tools", { DelayImageLoader: delayImageLoader });

Prostřednictvím zpracování události error (chyba) pro každý obrázek můžete zjistit chybu při načítání a zahájit stahování obrázku pomocí objektu XmlHttpRequest. Po stažení dat můžete vytvořit místní soubor a znovu spustit načtení obrázku.

Přidání filtrů

Na závěr je nutné změnit způsob propojení dat s ovládacím prvkem ListView, aby bylo možné zohlednit filtry. K tomu můžete použít funkci createFiltered objektu WinJS.Binding.List:

updateLayout: function (element, viewState) {
    updateSize();

    var listView = element.querySelector(".cardsList").winControl;

    var sorted = expansion.cardsList.createSorted(sortFunction);
    var filtered = sorted.createFiltered(filterFunction);

    if (viewState === appViewState.snapped) {
    } else {
        ui.setOptions(listView, {
            itemDataSource: filtered.dataSource,
            layout: new ui.GridLayout({ groupHeaderPosition: "top" })
        });
    }
}

Tento kód používá funkci filterFunction:

var filterFunction = function (item) {
     var result = true;
     //Filters
     for (var index = 0; index < filters.length; index++) {
         var filter = document.getElementById(filters[index][0] + "Filter").value;

         if (filter.substring(0, 3) != "All") {
             result &= (item[filters[index][0]] == filter);
         }
     }
     // Text
     var textFilter = document.getElementById("textFilter").value.toLowerCase();
     if (textFilter != "") {
         result &= (item.name.toLowerCase().indexOf(textFilter) != -1
             || item.text.toLowerCase().indexOf(textFilter) != -1
             || item.flavor.toLowerCase().indexOf(textFilter) != -1);
     }
     return result;
 }

Namísto přímých odkazů na vlastnosti používá tato funkce pole filtrů deklarované následujícím způsobem:

var filters = [["color", "colors"], ["author", "authors"], ["rarity", "rarities"], ["type", "types"]];

Pomocí tohoto pole lze snadno přidat nové filtry. Zpracování textového filtru probíhá samostatně, protože se vztahuje na více než jednu vlastnost.

Pomocí pole filters jsou také naplněna pole se seznamem:

// Filters
for (var index = 0; index < filters.length; index++) {
    prepareFilter(filters[index][0], filters[index][1], that);
}var prepareFilter = function (property, plural, that) {
    var filter = document.getElementById(property + "Filter");
    var results = [];



    for (var cardIndex = 0; cardIndex < expansion.cards.length; cardIndex++) {
        var value = expansion.cards[cardIndex][property];

        if (results.indexOf(value) == -1)
            results.push(value);
    }

    results.push("All " + plural);
    var sortedResults = results.sort(function (i0, i1) {
        if (i0 == i1)
            return 0;

        if (i0.substring(0, 3) == "All")
            return -1;

        if (i1.substring(0, 3) == "All")
            return 1;

        if (i0 > i1)
            return 1;

        return -1;
    });

    for (var index = 0; index < sortedResults.length; index++) {
        filter.options[index] = new Option(sortedResults[index]);
    }

    filter.addEventListener("change", function () {
        that.updateLayout(document, appView.value);
    });
};

Obrazovka karty

Obrazovka karty je velmi jednoduchá:

S uživatelským rozhraním propojuje kartu následující kód (přímé mapování):

(function () {
    "use strict";

    var appView = Windows.UI.ViewManagement.ApplicationView;
    var appViewState = Windows.UI.ViewManagement.ApplicationViewState;
    var nav = WinJS.Navigation;
    var ui = WinJS.UI;
    var utils = WinJS.Utilities;

    var card;

    ui.Pages.define("/pages/card/card.html", {
        ready: function (element, options) {
            card = options.card;

            document.getElementById("pageTitle").innerText = card.name;

            document.querySelector("#picture").src = card.logo;
            document.querySelector(".item-number").innerText = card.number + " / " +  
		card.expansion.cards.length;
            document.querySelector(".item-type").innerText = card.type;
            document.querySelector(".item-color").innerText = card.color;
            document.querySelector(".item-power").innerText = card.power;
            document.querySelector(".item-text").innerText = card.text;
            document.querySelector(".item-flavor").innerText = card.flavor;
            document.querySelector(".item-author").innerText = card.author;
       
            document.querySelector("#expansion-picture").src = card.expansion.banner;

            this.updateLayout(element, appView.value);
        },

        updateLayout: function (element, viewState) {

            if (viewState === appViewState.snapped) {
            } else {
            }
        }

    });
})();

Docela jednoduché, že?


Anglický originál tohoto článku od David Catuhe je dostupný na MSDN.


win8 soutěž win8 vývoj

Nový komentář

Pro přidání komentáře se musíte  registrovat Facebookem. Je to snadné a bezpečné.