Creating A Complete Web App In Foundation For Apps

About The Author

Stephen Saucier is forever torn between Australia & the USA, and he’s currently building web applications for Gather along with freelancing and speaking. He … More about Stephen ↬

Email Newsletter

Weekly tips on front-end & UX.
Trusted by 200,000+ folks.

Because Foundation for Apps was only released at the end of 2014, it hasn’t yet seen widespread usage, so there are few good sources of information on using the framework. In this article, Stephen Saucier brings you a comprehensive guide to build a functional web app with Foundation for Apps from start to finish. The techniques detailed here are fundamental to building practically any kind of app for any client, and this tutorial also serves as a strong introduction to the wider world of AngularJS and single-page apps.

Foundation for Apps is a new single-page app framework from Zurb that is closely related to Foundation 5 (also known as Foundation for Sites, a widely used front-end framework). It’s built around AngularJS and a flexbox grid framework. It’s intended to make creating a web app very quick and simple, enabling us to quickly start writing the code that’s unique to our application, rather than boilerplate.

Because Foundation for Apps was only released at the end of 2014, it hasn’t yet seen widespread usage, so there are few good sources of information on using the framework. This article is meant to be a comprehensive guide to building a functional web app with Foundation for Apps from start to finish. The techniques detailed here are fundamental to building practically any kind of app for any client, and this tutorial also serves as a strong introduction to the wider world of AngularJS and single-page apps.

In light of the new film being released later this year, we’ll be building a Star Wars knowledge base. It will be a responsive web application, using a RESTful API, caching and many of the features that Foundation for Apps and AngularJS offer.

Prefer to skip to the good stuff?

Getting Started

Take a quick squiz through the official documentation, which explains the styling aspects well but doesn’t go into detail about application functionality. Also, keep AngularJS’ excellent documentation handy, bearing in mind that Foundation for Apps includes some services that aren’t standard and that some AngularJS functions might not work out of the box. Keep in mind also that, natively, AngularJS and Foundation for Apps aren’t particularly well suited to apps that need significant SEO, since most content is loaded via AJAX.

Our app will get its data from the handy Star Wars API found at SWAPI. Take a look through SWAPI’s documentation to get a feel for the data served and its structure. Our application will be based on that structure for simplicity.

First, let’s install Foundation for Apps and create our project. Make sure that Ruby (already on OS X by default) and Node.js are installed, and then follow the four-step process detailed in the documentation. It’s pretty straightforward, even if you haven’t used the command line before. Once you’ve finished the process, your browser should display the default home page of your application at https://localhost:8080/#!/.

Foundation for Apps’ default home page
Foundation for Apps’ default home page (View large version)

Let’s get acquainted with the project’s files and folders.

The only file in our app’s base directory that we need to pay attention to is gulpfile.js, which gives instructions to the Gulp process that we’ve already used to start the server for our app. Gulp is a build system, and it’s very similar to Grunt. Later on, if we want to add some AngularJS modules or plugins, we’ll need to update this Gulp file with references to the JavaScript or CSS files for those modules or plugins.

The client folder is where we’ll find all of the other files we’re concerned with:

  • clients/assets/js/app.js is where our controller, directives and custom filters will be for this application;
  • All of our app’s SCSS can be found in clients/assets/scss, naturally;
  • clients/index.html is the base template for our application;
  • clients/templates/ is where we’ll find the template for all of our pages, most of which haven’t been created yet.
  • ## Build The Template And Home Screen

    Let’s start building! First, modify the index.html page, which doesn’t start out very well optimized for a real application. We’ll add an off-canvas menu for small screens, a button to toggle its opening, and a nice fade effect using classes from Foundation for Apps’ “Motion UI.” You can copy the code from the index.html file in our repository.

    We’ll add some SCSS variables to our _settings.scss file to set our colors, fonts and breakpoints:

    
    $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;
    

    Now we’ll trick out app.scss to add a bit of pizzazz. You can use the file in the repository as an example.

    Let’s quickly overwrite the default home.html file in clients/templates/ with a simple menu of links to all of the pages we’ll build:

    
    ---
    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>
    

    Our template is now looking pretty unique to us now — no cookie-cutter Foundation at this point:

    Home page of application
    Our template in action — it’s got a bit of flavor. (View large version)

    Creating A List Of Films

    Now we’re cooking with gas. In our templates folder, create a template file for our first subpage: films.html. Paste this snippet at the top:

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

    This tells our app three things:

    1. In links to this page, we’ll refer to the page as films
    2. The URL will have two possible parameters: id (the ID of the film, according to our data) and p (the page number in the listing of all films).
    3. We’ll be using our own custom AngularJS controller, called FilmsCtrl, instead of the default blank one that Foundation for Apps creates automatically.

    Because we’re using our own controller, let’s go ahead and create one in app.js. Look through the controller below, which we’ll be using for both our list of films and our single-film pages. You can see that this controller keeps track of URL parameters, figures out what page of results we’re on, gets the necessary data (either a list of films or details of an individual film, depending on the URL’s parameters) from our external API, and returns it to our view using the $scope variable. Add it to app.js after the angular.module declaration closes:

    
    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("https://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("https://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("https://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.
    

    After saving app.js, you may need to restart your server using the terminal (Control + C to cancel the operation and then foundation-apps watch again) to ensure that your app includes the new template file you’ve created along with the new controller.

    And just like that, we have a fully functional controller that gets data from an external RESTful API source, caches the result in the browser’s session and returns the data to our view!

    Open up films.html again, and let’s start building the view of the data that we can now access. Begin by adding the base view, which will show a list of films. We can access all properties that we’ve added to our $scope variable, without prefixing them with $scope, such as (in this case) films, prevPage and nextPage. Add the following below the template’s existing content:

    
    <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>
    

    Bodacious! We’ve got a list of film names as well as pagination if there are multiple pages of data. But that’s not especially useful yet — let’s turn the film’s name into a link to that film’s page in our app.

    We plan to use the film’s ID as the id parameter in our URL, and we have access to the film’s url attribute, which happens to have the film’s ID as its last parameter before the final slash. But how do we grab only the ID out of the URL that we have access to? AngularJS makes it easy with custom filters. Let’s wrap our {{film.title}} in a link, add a ui-sref attribute (which sets up an internal link) and use our film.url data with a custom filter applied to it:

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

    Well, now our page is broken because our app doesn’t know what the lastdir and capitalize filters are. We need to define those filters in our app.js file, placed just after our controller:

    
    .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! We now have a list of films, each of which links to its respective film page.

    List of films
    Our linked list of films: Not sure why we bothered with the prequels. (View large version)

    However, that link just takes us to an empty page at the moment, because films.html hasn’t been set up to show a specific film, rather than the whole list. That’s our next step.

    Displaying Details Of A Single Film

    We’ve already set up all of the data we need for the single-film page in our FilmsCtrl controller in the $scope.film variable (which is the same as $scope['film']). So, let’s reuse films.html and add another section that’s visible only when the singular film variable is set. We’ll set each key-value pair to use `

    ` and `
    ` within a `
    ` because we’re not unsemantic swine. Remember also that some of film’s fields, such as characters, have multiple values in an array, so we’ll need to use ng-repeat for those to display each value. To link each character to its character page, we’ll use the same method that we used in the listing of films: Using our lastdir filter, we link to each character’s people page by his/her/its 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>
    

    But look, when we view a film’s entry now, the list of characters shows just a URL relating to the character, rather than the character’s name.

    Single film view, characters as URLs
    Who are these characters? This will not do. (View large version)

    We need to replace that text with the character’s name, but we don’t have that critical piece of data. Perhaps we could look up that URL using the same method we used to get our film in the first place — then, the data we receive from the call would contain the character’s name. Let’s open up app.js again and add a directive, which we’ll call 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 returns a single property from the data resulting from our $http.get call, and we can specify which property we want. To use this directive, we need to add it to the area within our ng-repeat, like so:

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

    Nice. We now have each character’s name instead of just a wild URL, and each links to its respective page. Now our single film view will be complete once the rest of the data fields are added to the view (see films.html in the repository for the rest).

    Fixed character names
    This is much better. (View large version)

    Refactoring Our Code For Reuse

    Looking through SWAPI’s documentation and our plans for the rest of the application, we clearly see that our controllers for all other pages will be extremely similar to this one, varying only in the category of data we’re getting.

    With that in mind, let’s move the code inside our films controller to its own function, called genericController, placed just before the last closing brackets of app.js. We also need to replace every instance of the string 'films' with the variable multiple (five instances) and 'film' with single (one instance), because they represent the multiple and singular forms of the entity of each page. This allows us to create very DRY, reusable code that’s also easier to read and understand.

    Now we can add a call in our FilmsCtrl controller to our new genericController function with our two variables (multiple and single versions of our data), passed as parameters:

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

    Excellent! We have a reusable controller that grabs the data we need for any given page and puts it in a usable format! We can now easily create our other pages’ controllers just after FilmsCtrl in the same way:

    
    .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');
    })
    

    Go ahead and create the template HTML files for planets, species, people, starships and vehicles in the same way we created films.html but referencing the fields in SWAPI’s docs for each respective category of data.

    Voila! All of our pages now show the correct data and interlink with one another!

    Completed application
    Our completed application (View large version)

    Final Notes

    Our application is complete! Our demo (linked to below) is hosted by Aerobatic, which exclusively targets front-end web applications. You’ll see in our repository that we’ve added some domain-specific options to take advantage of Aerobatic’s API gateway, which sets up a proxy that caches the API’s data on the server once we request it. Without caching, the application would be both latency-limited and request-limited (SWAPI allows only so many requests per domain, as do most other APIs), and because our data isn’t likely to change often, that server caching makes everything very speedy after the first load. Because we’ve limited our onload requests and images, even the first load will be acceptable on a slow connection, and on each page load, the header menu will stay on the page, making the application feel fast.

    In the demo and repository, you can see that we’ve also added another API call on the details pages, which grabs an image URL for each entity from a Google custom search that we set up to trawl Wookieepedia and StarWars.com. So, we’ve now got dynamic, highly relevant images showing up on each detail page.

    Take a look at the demo below, or look through the source code for some hidden goodies and more Foundation-specific tricks, or download the repository and build on it locally with your own improvements.

    Further Reading

    Smashing Editorial (ml, al, mrn)