Krypton Solid

Creación de una aplicación web completa en Foundation for Apps

Creación de una aplicación web completa en Foundation for Apps

Foundation for Apps es un nuevo marco de aplicación de una sola página de Zurb que está estrechamente relacionado con Fundación 5 (también conocido como Foundation for Sites, un marco de interfaz de usuario ampliamente utilizado). Está construido alrededor de AngularJS y un marco de cuadrícula Flexbox.

Foundation for Apps es una nueva aplicación de una sola página marco de Zurb que está estrechamente relacionado con Fundación 5 (también conocido como Foundation for Sites, un marco de interfaz de usuario ampliamente utilizado). Está construido alrededor de AngularJS y un marco de cuadrícula flexible. Su objetivo es hacer que la creación de una aplicación web sea muy rápida y simple, lo que nos permite comenzar a escribir rápidamente el código que es exclusivo de nuestra aplicación, en lugar de un estándar.

Otras lecturas en SmashingMag:

Debido a que Foundation for Apps solo se lanzó a fines de 2014, aún no ha tenido un uso generalizado, por lo que hay pocas fuentes de información buenas sobre el uso del marco. Este artículo está destinado a ser una guía completa para crear una aplicación web funcional con Foundation for Apps de principio a fin. Las técnicas detalladas aquí son fundamentales para crear prácticamente cualquier tipo de aplicación para cualquier cliente, y este tutorial también sirve como una sólida introducción al mundo más amplio de AngularJS y las aplicaciones de una sola página.

A la luz de la nueva película que se lanzará a finales de este año, construiremos un Base de conocimientos de Star Wars. Será una aplicación web receptiva, utilizando un Sosegado API, almacenamiento en caché y muchas de las funciones que Fundación para aplicaciones y oferta AngularJS.

¿Prefieres pasar a lo bueno?

Empezando

Eche un vistazo rápido a través del documentación oficial, que explica bien los aspectos de estilo pero no entra en detalles sobre la funcionalidad de la aplicación. Además, mantén Excelente documentación de AngularJS útil, teniendo en cuenta que Foundation for Apps incluye algunos servicios que no son estándar y que algunas funciones de AngularJS podrían no funcionar de forma inmediata. Tenga en cuenta también que, de forma nativa, AngularJS y Foundation for Apps no son particularmente adecuados para aplicaciones que necesitan un SEO significativo, ya que la mayor parte del contenido se carga a través de AJAX.

Nuestra aplicación obtendrá sus datos de la práctica API de Star Wars que se encuentra en SWAPI. Echa un vistazo a través de Documentación de SWAPI para tener una idea de los datos servidos y su estructura. Nuestra aplicación se basará en esa estructura por simplicidad.

Primero, instalemos Foundation for Apps y creemos nuestro proyecto. Asegúrate de eso Rubí (ya en OS X por defecto) y Node.js están instalados y luego siga el proceso de cuatro pasos detallado en la documentación. Es bastante sencillo, incluso si no ha utilizado la línea de comandos antes. Una vez que haya terminado el proceso, su navegador debería mostrar la página de inicio predeterminada de su aplicación en http://localhost:8080/#!/.

La página de inicio predeterminada de Foundation for Apps (Ver versión grande)

Conozcamos los archivos y carpetas del proyecto.

El único archivo en el directorio base de nuestra aplicación al que debemos prestar atención es gulpfile.js, que da instrucciones al Proceso de trago que ya hemos utilizado para iniciar el servidor de nuestra aplicación. Gulp es un sistema de compilación y es muy similar a Gruñido. Más adelante, si queremos agregar algunos módulos o complementos de AngularJS, necesitaremos actualizar este archivo Gulp con referencias a los archivos JavaScript o CSS para esos módulos o complementos.

La client carpeta es donde encontraremos todos los demás archivos que nos interesan:

  • clients/assets/js/app.js es donde estarán nuestro controlador, directivas y filtros personalizados para esta aplicación;
  • Todos los SCSS de nuestra aplicación se pueden encontrar en clients/assets/scss, naturalmente;
  • clients/index.html es la plantilla base para nuestra aplicación;
  • clients/templates/ es donde encontraremos la plantilla para todas nuestras páginas, la mayoría de las cuales aún no se han creado.

Cree la plantilla y la pantalla de inicio

¡Empecemos a construir! Primero, modifique el index.html página, que no comienza muy bien optimizada para una aplicación real. Agregaremos un menú fuera del lienzo para pantallas pequeñas, un botón para alternar su apertura y un agradable efecto de desvanecimiento usando clases de «Motion UI» de Foundation for Apps. Puede copiar el código del index.html archivo en nuestro repositorio.

Agregaremos algunas variables SCSS a nuestro _settings.scss file para establecer nuestros colores, fuentes y puntos de interrupción:


$primary-color: #000;
$secondary-color: #ffe306;
$body-background: $secondary-color;
$breakpoints: (
  small: rem-calc(600),
  medium: rem-calc(900),
  large: rem-calc(1200),
  xlarge: rem-calc(1440),
  xxlarge: rem-calc(1920),
);
$h1-font-size: rem-calc(80);
$body-font-family: "brandon-grotesque", Helvetica, Arial, sans-serif;
$header-font-family: "flood-std", Helvetica, sans-serif;

Ahora vamos a engañar app.scss para agregar un poco de dinamismo. Puedes usar el archivo en el repositorio como ejemplo.

Sobrescribamos rápidamente el valor predeterminado home.html presentar en clients/templates/ con un menú simple de enlaces a todas las páginas que crearemos:


---
name: home
url: /
---
<div class="grid-content">
  <h1>Star Wars Compendium</h1>
  <p class="lead">Foundation for Apps using Star Wars API</p>
  <hr>
  <ul class="home-list">
    <li><a ui-sref="films">Films</a></li>
    <li><a ui-sref="species">Species</a></li>
    <li><a ui-sref="planets">Planets</a></li>
    <li><a ui-sref="people">People</a></li>
    <li><a ui-sref="starships">Starships</a></li>
    <li><a ui-sref="vehicles">Vehicles</a></li>
  </ul>
</div>

Nuestra plantilla ahora se ve bastante única para nosotros ahora, sin una base de corte de galletas en este momento:

Página de inicio de la aplicación
Nuestra plantilla en acción, tiene un poco de sabor. (Ver versión grande)

Crear una lista de películas

Ahora estamos cocinando con gas. En nuestro templates carpeta, cree un archivo de plantilla para nuestra primera subpágina: films.html. Pegue este fragmento en la parte superior:


---
name: films
url: /films/:id?p=
controller: FilmsCtrl
---

Esto le dice a nuestra aplicación tres cosas:

  1. En los enlaces a esta página, nos referiremos a la página como films
  2. La URL tendrá dos posibles parámetros: id (el ID de la película, según nuestros datos) y p (el número de página en la lista de todas las películas).
  3. Usaremos nuestro propio controlador AngularJS personalizado, llamado FilmsCtrl, en lugar del en blanco predeterminado que Foundation for Apps crea automáticamente.

Debido a que estamos usando nuestro propio controlador, sigamos adelante y creemos uno en app.js. Mire a través del controlador a continuación, que usaremos para nuestros lista de películas y nuestro película única páginas. Puede ver que este controlador realiza un seguimiento de los parámetros de la URL, determina en qué página de resultados estamos, obtiene los datos necesarios (ya sea una lista de películas o detalles de una película individual, según los parámetros de la URL) de nuestra API externa y lo devuelve a nuestra vista utilizando el $scope variable. Añádelo a app.js después de la angular.module la declaración cierra:


controller('FilmsCtrl',
  ["$scope", "$state", "$http",function($scope, $state, $http){
  // Grab URL parameters - this is unique to FFA, not standard for
  // AngularJS. Ensure $state is included in your dependencies list
  // in the controller definition above.
  $scope.id = ($state.params.id || ’);
  $scope.page = ($state.params.p || 1);

  // If we're on the first page or page is set to default
  if ($scope.page == 1) {
    if ($scope.id != ’) {
      // We've got a URL parameter, so let's get the single entity's
      // data from our data source
      $http.get("http://swapi.co/api/"+'films'+"/"+$scope.id,
          {cache: true })
        .success(function(data) {
          // If the request succeeds, assign our data to the 'film'
          // variable, passed to our page through $scope
          $scope['film'] = data;
        })

    } else {
      // There is no ID, so we'll show a list of all films.
      // We're on page 1, so the next page is 2.
      $http.get("http://swapi.co/api/"+'films'+"/", { cache: true })
        .success(function(data) {
          $scope['films'] = data;
          if (data['next']) $scope.nextPage = 2;
        });
    }
  } else {
    // Once again, there is no ID, so we'll show a list of all films.
    // If there's a next page, let's add it. Otherwise just add the
    // previous page button.
    $http.get("http://swapi.co/api/"+'films'+"/?page="+$scope.page,
      { cache: true }).success(function(data) {
        $scope['films'] = data;
        if (data['next']) $scope.nextPage = 1*$scope.page + 1;
      });
      $scope.prevPage = 1*$scope.page - 1;
  }
  return $scope;

}])  // Ensure you don't end in a semicolon, because more
     // actions are to follow.

Después de guardar app.js, es posible que deba reiniciar su servidor usando la terminal (Control + C para cancelar la operación y luego foundation-apps watch nuevamente) para asegurarse de que su aplicación incluya el nuevo archivo de plantilla que ha creado junto con el nuevo controlador.

Y así, tenemos un controlador completamente funcional que obtiene datos desde una fuente API RESTful externa, almacena en caché el resultado en la sesión del navegador y devuelve los datos a nuestra vista!

Abrir films.html nuevamente, y comencemos a construir la vista de los datos a los que ahora podemos acceder. Comience agregando la vista base, que mostrará una lista de películas. Podemos acceder a todas las propiedades que hemos agregado a nuestro $scope variable, sin prefijarlos con $scope, como (en este caso) films, prevPage y nextPage. Agregue lo siguiente debajo del contenido existente de la plantilla:


<div class="grid-content films" ng-show="films">
  <a class="button pagination"
    ng-show="prevPage"
    ui-sref="films({ p: prevPage })">
    Back to Page {{prevPage}}
  </a>

  <div class="grid-content shrink">
    <ul class="menu-bar vertical">
      <li ng-repeat="film in films.results">
        {{film.title}}
      </li>
    </ul>
  </div>

  <a class="button pagination"
    ng-show="nextPage"
    ui-sref="films({ p: nextPage })">
    To Page {{nextPage}}
  </a>
</div>

¡Fabuloso! Tenemos una lista de nombres de películas, así como la paginación si hay varias páginas de datos. Pero eso no es especialmente útil todavía; convierta el nombre de la película en un enlace a la página de esa película en nuestra aplicación.

Planeamos usar la identificación de la película como el id parámetro en nuestra URL, y tenemos acceso a la película url atributo, que tiene el ID de la película como último parámetro antes de la barra final. Pero como agarramos solo el ID de la URL a la que tenemos acceso? AngularJS lo hace fácil con custom filtros. Envolvemos nuestro {{film.title}} en un enlace, agregue un ui-sref atributo (que establece un enlace interno) y utilice nuestro film.url datos con un filtro personalizado aplicado:


<a ui-sref="films({ id:( film.url | lastdir ), p:’ })">
  {{film.title | capitalize}}
</a>

Bueno, ahora nuestra página está rota porque nuestra aplicación no sabe qué lastdir y capitalize los filtros son. Necesitamos definir esos filtros en nuestro app.js archivo, colocado justo después de nuestro controlador:


.filter('capitalize', function() {
  // Send the results of this manipulating function
  // back to the view.
  return function (input) {
    // If input exists, replace the first letter of
    // each word with its capital equivalent.
    return (!!input) ? input.replace(/([^W_]+[^s-]*) */g,
      function(txt){return txt.charAt(0).toUpperCase() +
        txt.substr(1)}) : ’;
  }
})
.filter('lastdir', function () {
  // Send the results of this manipulating function
  // back to the view.
  return function (input) {
    // Simple JavaScript to split and slice like a fine chef.
    return (!!input) ? input.split('/').slice(-2, -1)[0] : ’;
  }
})

¡Bingo! Ahora tenemos una lista de películas, cada una de las cuales enlaza con su página de película respectiva.

Lista de películas
Nuestra lista vinculada de películas: No estoy seguro de por qué nos molestamos con las precuelas. (Ver versión grande)

Sin embargo, ese enlace solo nos lleva a una página vacía en este momento, porque films.html no se ha configurado para mostrar una película específica, en lugar de la lista completa. Ese es nuestro próximo paso.

Visualización de detalles de una sola película

Ya hemos configurado todos los datos que necesitamos para la página de una sola película en nuestro FilmsCtrl controlador en el $scope.film variable (que es lo mismo que $scope['film']). Entonces, reutilicemos films.html y agregue otra sección que sea visible solo cuando el singular film se establece la variable. Configuraremos cada par clave-valor para usar <dt> y <dd> dentro de una <dl> porque no somos cerdos no semánticos. Recuerde también que algunos de filmcampos de, como characters, tiene varios valores en una matriz, por lo que necesitaremos usar ng-repeat para que muestren cada valor. Para vincular cada personaje a su página de personaje, usaremos el mismo método que usamos en la lista de películas: Usando nuestro lastdir filtro, enlazamos a cada personaje people página por su ID.


<div class="grid-content film"
  ng-show="film" ng-init="crawl="false"">
  <h1>Episode {{film.episode_id | uppercase}}: 
    {{film.title}}</h1>
  <hr>
  <dl>
    <dt>Opening Crawl</dt>
    <dd ng-class="{'crawl':crawl === true}" 
      ng-click="crawl === true ? crawl = false : crawl = true">
      {{film.opening_crawl}}</dd>
    <dt>Director</dt>
    <dd>{{film.director | capitalize}}</dd>
    <dt>Producer</dt>
    <dd>{{film.producer | capitalize}}</dd>
    <dt>Characters</dt>
    <dd ng-repeat="character in film.characters"
      ui-sref="people({ id:(character | lastdir), p:’})">
      {{character}}
    </dd>
  </dl>
</div>

Pero mire, cuando vemos la entrada de una película ahora, la lista de personajes muestra solo una URL relacionada con el personaje, en lugar del nombre del personaje.

Vista de una sola película, personajes como URL
¿Quiénes son estos personajes? Esto no lo hará. (Ver versión grande)

Necesitamos reemplazar ese texto con el nombre del personaje, pero no tenemos ese dato crítico. Quizás podríamos buscar esa URL usando el mismo método que usamos para obtener nuestra película en primer lugar; luego, los datos que recibimos de la llamada contendrían el nombre del personaje. Vamos a abrir app.js de nuevo y agregue un directiva, que llamaremos getProp.


.directive("getProp", ['$http', '$filter', function($http, $filter) {
  return {
    // All we're going to display is the scope's property variable.
    template: "{{property}}",
    scope: {
      // Rather than hard-coding 'name' as in 'person.name', we may need
      // to access 'title' in some instances, so we use a variable (prop) instead.
      prop: "=",
      // This is the swapi.co URL that we pass to the directive.
      url: "="
    },
    link: function(scope, element, attrs) {
      // Make our 'capitalize' filter usable here
      var capitalize = $filter('capitalize');
      // Make an http request for the 'url' variable passed to this directive
      $http.get(scope.url, { cache: true }).then(function(result) {
        // Get the 'prop' property of our returned data so we can use it in the template.
        scope.property = capitalize(result.data[scope.prop]);
      }, function(err) {
        // If there's an error, just return 'Unknown'.
        scope.property = "Unknown";
      });
    }
  }
}])

getProp devuelve una sola propiedad de los datos resultantes de nuestro $http.get call, y podemos especificar qué propiedad queremos. Para usar esta directiva, debemos agregarla al área dentro de nuestra ng-repeat, al igual que:


<span get-prop prop="'name'" url="character">{{character}}</span>

Lindo. Ahora tenemos el nombre de cada personaje en lugar de solo una URL salvaje, y cada uno tiene enlaces a su página respectiva. Ahora nuestro single film La vista estará completa una vez que el resto de los campos de datos se agreguen a la vista (consulte films.html en el repositorio para el resto).

Nombres de personajes fijos
Esto es mucho mejor. (Ver versión grande)

Refactorización de nuestro código para su reutilización

Mirando a través de Documentación de SWAPI y nuestros planes para el resto de la aplicación, vemos claramente que nuestros controladores para todas las demás páginas serán extremadamente similares a esta, variando solo en la categoría de datos que estamos obteniendo.

Con eso en mente, movamos el código dentro de nuestro films controlador a su propia función, llamada genericController, colocado justo antes de los últimos corchetes de cierre de app.js. También necesitamos reemplazar cada instancia de la cadena 'films' con la variable multiple (cinco instancias) y 'film' con single (una instancia), porque representan las formas múltiples y singulares de la entidad de cada página. Esto nos permite crear muy SECO, código reutilizable que también es más fácil de leer y comprender.

Ahora podemos agregar una llamada en nuestro FilmsCtrl controlador a nuestro nuevo genericController función con nuestras dos variables (multiple y single versiones de nuestros datos), pasados ​​como parámetros:


.controller('FilmsCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'films', 'film');
})

¡Excelente! Tenemos un controlador reutilizable que toma los datos que necesitamos para cualquier página y los pone en un formato utilizable. Ahora podemos crear fácilmente los controladores de nuestras otras páginas justo después FilmsCtrl del mismo modo:


.controller('SpeciesCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'species', 'specie');
})
.controller('PlanetsCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'planets', 'planet');
})
.controller('PeopleCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'people', 'person');
})
.controller('StarshipsCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'starships', 'starship');
})

Continúe y cree los archivos HTML de plantilla para planets, species, people, starships y vehicles de la misma manera que creamos films.html pero haciendo referencia a los campos en Documentos de SWAPI para cada categoría respectiva de datos.

¡Voila! ¡Todas nuestras páginas ahora muestran los datos correctos y están interconectadas entre sí!

Solicitud completa
Nuestra solicitud completa (Ver versión grande)

Notas finales

¡Nuestra aplicación está completa! Nuestra demostración (vinculada a continuación) está alojada por Acrobático, que se dirige exclusivamente a aplicaciones web front-end. Verá en nuestro repositorio que hemos agregado algunas opciones específicas de dominio para aprovechar la puerta de enlace API de Aerobatic, que configura un proxy que almacena en caché los datos de la API en el servidor una vez que lo solicitamos. Sin el almacenamiento en caché, la aplicación estaría limitada por latencia y por solicitud (SWAPI solo permite un número determinado de solicitudes por dominio, al igual que la mayoría de las otras API), y debido a que es poco probable que nuestros datos cambien con frecuencia, el almacenamiento en caché del servidor hace que todo sea muy Rápido después de la primera carga. Porque hemos limitado nuestro onload solicitudes e imágenes, incluso la primera carga será aceptable en una conexión lenta, y en cada carga de página, el menú de encabezado permanecerá en la página, haciendo que la aplicación se sienta rápida.

En la demostración y el repositorio, puede ver que también hemos agregado otra llamada a la API en las páginas de detalles, que toma una URL de imagen para cada entidad de una búsqueda personalizada de Google que configuramos para rastrear. Wookieepedia y StarWars.com. Entonces, ahora tenemos imágenes dinámicas y altamente relevantes que se muestran en cada página de detalles.

Eche un vistazo a la demostración a continuación, o busque en el código fuente algunos beneficios ocultos y más trucos específicos de la Fundación, o descargue el repositorio y desarrolle localmente con sus propias mejoras.

Deja un comentario