Seleccionar página

Después de probar muchas maneras de hacer aplicaciones para Windows 8 en HTML5 (Plantillas por defecto, solo con navigator.js, MV* como Backbone, mi propio MVR con WinjsMVR) con sus más y sus menos, con errores y cosas que salieron bastante bien.. creo que hace poco llegué al sistema que me gusta y considero a día de hoy el más útil para trabajar.

Los otros métodos

Plantillas por defecto realmente no estoy seguro de tener una razón concreta pero nunca me convencieron, me parecían incómodas y no me gustaba como estaban estructuradas.
Solo navigator.js demasiado simple cuando quieres hacer una app mínimamente compleja, sobretodo que tienes que hacer tu mismo todo lo que ya te viene hecho con los controles.
MV* Hasta hace poco para mi ha sido la solución más efectiva, pero al final las cosas que con WinJS son sencillas de hacer se me hacían complicadas.

Método actual

Hace poco decidí volver a utilizar WinJS en su totalidad, por probar, y me acabó gustando mucho el resultado, las cosas que antes se me hacían complicadas (cosas típicas en las apps de Windows 8) ahora el propio control ya las tenía. No me encanta la estructura que se da por hecho para este tipo de apps, pero la he podido ir moldeando hasta tener algo que me convence bastante (aunque seguro que sigo intentando mejorarla poco a poco). Lo he juntado todo en una plantilla gratuita a la que he llamado Fluid WinJS Template que os podéis descargar y que seguramente iré modificando a medida que se me ocurra y que he puesto en GitHub por si alguien tiene sugerencias y/o ideas

Fluid WinJS

A día de hoy así es como lo he estructurado (depende cuando leas esto quizá se ha modificado alguna cosa). Estructura de archivos en debug


|-- scss // Opcional cualquier preprocesador
    |-- default.scss
    |-- file2.scss
    |-- ... 
    |-- pages
        |-- home.scss
        |-- page.scss
|-- css
    |-- styles.css.bundle
    |-- default.css
    |-- file2.css
    |-- ... 
    |-- pages
        |-- home.css
        |-- page.css
|-- images
|-- js
    |-- code.js.bundle
    |-- default.js
    |-- Data.js
    |-- Lists.js
    |-- navigator.js
    |-- pages
        |-- home.js
        |-- page.js
|-- pages
    |-- home.html
    |-- page.html
|-- default.html

Estructura de archivos en production


|-- css
    |-- styles.min.css
|-- images
|-- js
    |-- code.min.js
|-- pages
    |-- home.html
    |-- page.html
|-- default_production.html

Después de probar y probar me decidí por esta estructura bastante básica y sencilla, que no es exactamente cómo cuando programo para webs, pero que se parece lo justo y necesario a la estructura recomendada para las apps de Windows 8 en HTML5 y me deja seguir trabajando de manera cómoda

Resumen de archivos

scss básicamente es la carpeta con todos los scss (o cualquier preprocesador que uses) esto no lo he incluido en la plantilla para no forzar a usar ningún preprocesador.
css es la carpeta con los archivos css de la app, dentro tiene una subcarpeta con los css de cada página.
css/styles.css.bundle una de las herramientas de Web essentials for visual Studio
js la carpeta con todos los archivos js propios que tengo en mi app.
js/code.js.bundle lo mismo que hemos visto para css pero con nuestros archivos de JavaScript
js/default.js inicializo mi aplicación, junto a Data.js y Lists.js
js/Data.js en este archivo recojo toda la info necesaria que necesita mi app para empezar, por ejemplo una llamada ajax para el contenido necesario que mostraré en la home.
js/Lists.js la mayoría de apps en Windows 8 se basan en crear listas con la info (en este caso la info de Data.js)
js/navigator.js la librería de navegación que nos incluye por defecto la plantilla de navegación.
js/pages los js correspondientes a cada página.

Análisis de algunos archivos (en github está todo completo)

css/styles.css.bundle


<?xml version="1.0" encoding="utf-8"?>
<bundle minify="true" runOnBuild="true" output="styles.css" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/bundle.xsd">

    <!-- Include all css of your project -->
    <file>/css/default.css</file>


<!-- Include all JS files of your pages --> <!-- <file>/css/pages/#.css</file> --> <file>/css/pages/home.css</file> <file>/css/pages/page.css</file> </bundle>

js/code.js.bundle


<?xml version="1.0" encoding="utf-8"?>

<bundle minify="true" runOnBuild="true" output="code.js" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/bundle.xsd">
 

 <!-- Include all JS of your Scripts folder -->
 <!-- <file>/Scripts/#.js</file> -->

 
 <!-- Template files -->
 <file>/js/Data.js</file>
 <file>/js/default.js</file>
 <file>/js/Lists.js</file>
 <file>/js/navigator.js</file>

 
 <!-- Include all JS files of your pages -->
 <!-- <file>/js/pages/#.js</file> -->
 <file>/js/pages/home.js</file>
 <file>/js/pages/page.js</file>
</bundle>

En estos dos archivos incluyo los correspondientes js o css para luego poder tener mi versión de producción con todo minificado y concatenado.
js/default.js


(app, nav) {
   "use strict";

   app.onactivated = function (args) {

   // Recojo los datos, creo las listas y proceso la UI
   WinJS.Promise.sequence([Data.initialize, Lists.initialize, WinJS.UI.processAll])
     .then(function () {

         // Una vez ha terminado todo voy a la home
         nav.navigate(Application.navigator.home);
     });
   };

   app.oncheckpoint = function (args) {};

   app.start();

   // WinJS secuence https://github.com/winjs/winjs/issues/165#issuecomment-40871159
   // Es parecido al join pero cada promise espera a que termine la anterior
   WinJS.Promise.sequence = function (operations) {
       return operations.reduce(function (p, op) {
       return p.then(op);
   }, WinJS.Promise.as());
  };
})(WinJS.Application, WinJS.Navigation);

js/Data.js


(function () {
   'use strict';

   // Creo el namespace de Data
   WinJS.Namespace.define("Data", {
       initialize: initialize,
   });

   // Función que inicia los datos que he de cargar primero
   function initialize() {
       // devuelvo una promise así puedo tener tanto llamadas síncronas como asíncronas y luego devolverlo todo
       return new WinJS.Promise(function (complete) {

           // Ejemplo que hace un get de datos sin hacer una llamada ajax
           Data.itemList = getItems();
 
           /* Este ejemplo sería el de una llamada ajax
           getItemsAsync().then(function (results) {
               Data.itemsAsync = results;
           }).then(complete);
           */

           // Get ...

           // Complete (but check async methods..) Si no hago ninguna llamada ajax acabo así, sino he de terminar después del ajax.
           complete();
       });
   }

   // Data
   function getItems() {
       return [
           { title: "Marvelous Mint", text: "Gelato", picture: "/images/fruits/60Mint.png" },
           { title: "Succulent Strawberry", text: "Sorbet", picture: "/images/fruits/60Strawberry.png" },
           { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "/images/fruits/60Banana.png" },
           { title: "Lavish Lemon Ice", text: "Sorbet", picture: "/images/fruits/60Lemon.png" },
           { title: "Creamy Orange", text: "Sorbet", picture: "/images/fruits/60Orange.png" },
           { title: "Very Vanilla", text: "Ice Cream", picture: "/images/fruits/60Vanilla.png" },
           { title: "Banana Blast", text: "Low-fat frozen yogurt", picture: "/images/fruits/60Banana.png" },
           { title: "Lavish Lemon Ice", text: "Sorbet", picture: "/images/fruits/60Lemon.png" }
       ];
   }

   function getItemsAsync() {
       return WinJS.xhr({
           url: '',
           type: 'GET'
       });
   }
})();

js/Lists.js


  (function () {
      'use strict';

       // Creo el namespace de Lists
       WinJS.Namespace.define("Lists", {
           initialize: initialize,
       });

       // Función que inicia las listas
      function initialize() {
           return new WinJS.Promise(function (complete) {

              // Creo cada lista con lo que hemos conseguido de Data
              Lists.ItemList = {
                  items: new WinJS.Binding.List(Data.itemList)
              };

             // List ...

             // Complete
             complete();
        });
   }
})();

pages/home.html


<div class="page">
   <header role="banner">
       <h1 class="titlearea win-type-ellipsis">
           <span class="pagetitle">Home</span>
       </h1>
   </header>

   <!-- Este control me permite tener pequeñas islas de información con cualquier estructura,
   puedo poner HTML directamente, otro control, etc.. también añade los headers, controla el scroll y
   demás propiedades que dan los controles en WinJS me ha parecido un control ensencial -->
   <div data-win-control="WinJS.UI.Hub">
       
       <!-- Este control es una de esas zona de contenido con una Listview en su interior -->
       <div data-win-control="WinJS.UI.HubSection" data-win-options="{header: 'list'}">
           <div data-win-control="WinJS.UI.ListView" data-win-options="{
               itemDataSource: Lists.ItemList.items.dataSource
           }"></div>
       </div>

       <!-- Este control tiene HTML simple en su interior -->
       <div data-win-control="WinJS.UI.HubSection" data-win-options="{header: 'paragraph'}">
           <p>Home page</p>
           <a class="navigator" data-page="/pages/page/page.html" href="#">Go to page</a>
       </div>
   </div>
   <!-- Se pueden cambiar las características del control mediante js y css para que no todas
   las apps tengan el mismo aspecto -->
</div>

Conclusiones

Gracias a lo que han mejorado WinJS en Windows 8.1 he podido, por fin, hacerme una plantilla con la que me sienta a gusto, sobretodo con el Hub, desde luego no se si es la manera más acertada, pero es la que me está funcionando mejor y espero que a vosotros también.
Puedes ver un ejemplo de app creado con esta plantilla.
Descarga el template y empieza a usarlo ya mismo.
Échale un vistazo en GitHub.

 

0