导航:[首页]->[wingui]->[Windows 8官方Sample框架解析]

下载源码

Windows 8 app samples\File picker sample为例说明

default.html

先看下default.html为了节省篇幅,源码有删减,下同】。

<head>
    <script src="/sample-utils/sample-utils.js"></script>
    <script src="/js/default.js"></script>
</head>
<body role="application">
    <div id="rootGrid">
        <!-- 通用的header -->
        <div id="header" role="contentinfo" data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/sample-utils/header.html'}"></div>

        <div id="content">
            <h1 id="featureLabel"></h1>
            <div id="contentHost"></div>
        </div>

        <!-- 通用的footer -->
        <div id="footer" data-win-control="WinJS.UI.HtmlControl" data-win-options="{uri: '/sample-utils/footer.html'}"></div>
    </div>
</body>

sample-utils/footer.htmlsample-utils/header.html 分别定义了footer和header,内容就是普通的html。例如header.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
    </head>
    <body>
        <img src="/images/windows-sdk.png" alt="Windows Logo" />
        <span>Windows SDK Samples</span>
    </body>
</html>

需要注意的是WinJS.UI.HtmlControl控件,它可以显示静态html页面。

接下来就是/sample-utils/sample-utils.js/js/default.js。其中sample-utils.js在default.js之前。实际上,大部分逻辑都定义在sample-utils.js中。

(function () {
    "use strict";

    // 标题,在/sample-utils/sample-utils.js被使用。
    var sampleTitle = "File picker JS sample";

    // 具体若干种"文件选择"操作,每一种操作都通过一个html/js/css集合来实现
    var scenarios = [
        { url: "/html/scenario1.html", title: "Pick a single photo" },
        { url: "/html/scenario2.html", title: "Pick multiple files" },
        { url: "/html/scenario3.html", title: "Pick a folder" },
        { url: "/html/scenario4.html", title: "Save a file" },
        { url: "/html/scenario5.html", title: "Open a cached file" },
        { url: "/html/scenario6.html", title: "Update a cached file" }
    ];

    // 程序被激活时调用
    function activated(eventObject) {
        if (eventObject.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            // Use setPromise to indicate to the system that the splash screen must not be torn down
            // until after processAll and navigate complete asynchronously.
            eventObject.setPromise(WinJS.UI.processAll().then(function () {
                // Navigate to either the first scenario or to the last running scenario
                // before suspension or termination.
                // 恢复上次的url,若不存在,则使用第一个
                var url = WinJS.Application.sessionState.lastUrl || scenarios[0].url;
                // 浏览该url,这将触发navigated事件,见下面分析
                return WinJS.Navigation.navigate(url);
            }));
        }
    }

    // WinJS.Navigation的 navigated事件处理函数
    WinJS.Navigation.addEventListener("navigated", function (eventObject) {
        var url = eventObject.detail.location;

        // 若id=contentHost的元素存在,则清空它
        // 该元素在default.html中定义
        var host = document.getElementById("contentHost");
        // Call unload method on current scenario, if there is one
        host.winControl && host.winControl.unload && host.winControl.unload();
        WinJS.Utilities.empty(host);

        eventObject.detail.setPromise(
            // 将改元素重新渲染为新的url(html文件)
            WinJS.UI.Pages.render(url, host, eventObject.detail.state).then(function () {
                // 同时重置最近的url记录
                WinJS.Application.sessionState.lastUrl = url;
            }
        ));
    });

    // 将“标题“和”操作“数组放入一个特定的名字空间中,其他js必须通过这个名字空间访问
    WinJS.Namespace.define("SdkSample", {
        sampleTitle: sampleTitle,
        scenarios: scenarios
    });

    // 注册激活事件
    WinJS.Application.addEventListener("activated", activated, false);
    // 开始运行
    WinJS.Application.start();
})();

scenario1.html

在最开始时,若不存在最近的url,那么会使用第一个页面,也就是scenario1.html,接下来我们将以scenario1.html为例说明,scenario2.html...等类同。代码非常简单:

<head>
    <title></title>
    <script src="/js/scenario1.js"></script>
</head>
<body>
    <div data-win-control="SdkSample.ScenarioInput">
        <p>Prompt the user to pick a single photo.</p>
        <button class="action" id="open">Pick photo</button>
        <br /><br />
    </div>
    <div data-win-control="SdkSample.ScenarioOutput">
    </div>
</body>
</html>

scenario1.js

(function () {
    "use strict";
    var page = WinJS.UI.Pages.define("/html/scenario1.html", {
        // 在页面就绪时,给scenario1.html的按钮注册个事件
        ready: function (element, options) {
            document.getElementById("open").addEventListener("click", pickSinglePhoto, false);
        }
    });

    function pickSinglePhoto() {
    }
})();

这其中的重点就是两个data-win-control,分别是SdkSample.ScenarioInputSdkSample.ScenarioOutput,这两个控件在sample-utils.js中定义。

sample-utils.js

(function () {

    // 定义控件ScenarioInput
    var ScenarioInput = WinJS.Class.define(
        // 构造函数
        function (element, options) {
        element.winControl = this;
        this.element = element;

        new WinJS.Utilities.QueryCollection(element)
                    .setAttribute("role", "main")
                    .setAttribute("aria-labelledby", "inputLabel");
        element.id = "input";

        // 调用三个成员函数
        this.addInputLabel(element);
        this.addDetailsElement(element);
        this.addScenariosPicker(element);
    }, {
        addInputLabel: function (element) {
            var label = document.createElement("h2");
            label.textContent = "Input";
            label.id = "inputLabel";
            element.parentNode.insertBefore(label, element);
        },
        addScenariosPicker: function (parentElement) {
            var scenarios = document.createElement("div");
            scenarios.id = "scenarios";
            // 在这里创建了一个列表选择器,见后续分析
            var control = new ScenarioSelect(scenarios);

            parentElement.insertBefore(scenarios, parentElement.childNodes[0]);
        },
    }
    );


    // 定义控件ScenarioOutput
    var ScenarioOutput = WinJS.Class.define(
        function (element, options) {
    }, {
    }
    );

    var currentScenarioUrl = null;

    // 在导航之前,先设置currentScenarioUrl变量,考虑到
    // scenario1.html,scenario2.html ...中分别定义了一个列表控件,为了同步
    // 所选择的item,这里使用一个变量记录当前选择的item
    // http://msdn.microsoft.com/en-us/library/windows/apps/br229843.aspx
    WinJS.Navigation.addEventListener("navigating", function (evt) {
        currentScenarioUrl = evt.detail.location;
    });

    // Control that populates and runs the scenario selector

    // 定义一个“列表选择”控件,这里使用WinJS.UI.Pages
    var ScenarioSelect = WinJS.UI.Pages.define("/sample-utils/scenario-select.html", {
        ready: function (element, options) {
            var that = this;

            // 获得scenario-select.html中id = scenarioSelect的控件
            /*
            <html xmlns="http://www.w3.org/1999/xhtml">
                <head>
                    <title></title>
                </head>
                <body>
                    <div class="options">
                         <h3 id="listLabel">Select scenario:</h3>
                        <select id="scenarioSelect" aria-labelledby="listLabel">
                            <!-- scenario list is inserted here -->
                        </select>
                    </div>
                </body>
            </html>
            */
            var selectElement = WinJS.Utilities.query("#scenarioSelect", element);
            this._selectElement = selectElement[0];

            // 插入所有记录
            // SdkSample.scenarios在default.js中定义
            SdkSample.scenarios.forEach(function (s, index) {
                // 见成员函数_addScenario
                that._addScenario(index, s);
            });

            //设置事件处理
            selectElement.listen("change", function (evt) {
                var select = evt.target;
                if (select.selectedIndex >= 0) {
                    var newUrl = select.options[select.selectedIndex].value;

                    // 浏览到新的url
                    WinJS.Navigation.navigate(newUrl).then(function () {
                        setImmediate(function () {
                            // 让该控件获得焦点
                            document.getElementById("scenarioSelect").setActive();
                        });
                    });
                }
            });

            // 最多显示5条记录,若更多会显示滑动条
            selectElement[0].size = (SdkSample.scenarios.length > 5 ? 5 : SdkSample.scenarios.length);
            if (SdkSample.scenarios.length === 1) {
                // Avoid showing down arrow when there is only one scenario
                selectElement[0].setAttribute("multiple", "multiple");
            }
        },

        _addScenario: function (index, info) {
            var option = document.createElement("option");
            if (info.url === currentScenarioUrl) {
                // 是否选择
                option.selected = "selected";
            }
            option.text = (index + 1) + ") " + info.title;
            option.value = info.url;
            this._selectElement.appendChild(option);
        }
    });

    // 初始化标题,featureLabel在default.html中定义
    function activated(e) {
        WinJS.Utilities.query("#featureLabel")[0].textContent = SdkSample.sampleTitle;
    }
    // 设置“激活”事件回调
    WinJS.Application.addEventListener("activated", activated, false);

    // 导出两个控件
    // Export public methods & controls
    WinJS.Namespace.define("SdkSample", {
        ScenarioInput: ScenarioInput,
        ScenarioOutput: ScenarioOutput
    });

})();