Menu Search
Jump to the content X X
Smashing Conf New York

You know, we use ad-blockers as well. We gotta keep those servers running though. Did you know that we publish useful books and run friendly conferences — crafted for pros like yourself? E.g. our upcoming SmashingConf New York, dedicated to smart front-end techniques and design patterns.

Creating A Complete Web App In Foundation For Apps

Foundation for Apps is a new single-page app1 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.

Further Reading on SmashingMag: Link

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 RESTful5 API, caching and many of the features that Foundation for Apps6 and AngularJS offer.

Prefer to skip to the good stuff?

Getting Started Link

Take a quick squiz through the official documentation9, which explains the styling aspects well but doesn’t go into detail about application functionality. Also, keep AngularJS’ excellent documentation10 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 SWAPI11. Take a look through SWAPI’s documentation3512 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 Ruby13 (already on OS X by default) and Node.js14 are installed, and then follow the four-step process detailed in the documentation15. 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 http://localhost:8080/#!/.

Foundation for Apps’ default home page16
Foundation for Apps’ default home page (View large version17)

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 process18 that we’ve already used to start the server for our app. Gulp is a build system19, and it’s very similar to Grunt20. 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 Link

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

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 repository22 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 application23
Our template in action — it’s got a bit of flavor. (View large version24)

Creating A List Of Films Link

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("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.

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 filters25. 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 films26
Our linked list of films: Not sure why we bothered with the prequels. (View large version27)

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 Link

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 <dt> and <dd> within a <dl> because we’re not unsemantic swine28. 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 URLs29
Who are these characters? This will not do. (View large version30)

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 directive31, 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 repository32 for the rest).

Fixed character names33
This is much better. (View large version34)

Refactoring Our Code For Reuse Link

Looking through SWAPI’s documentation3512 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 DRY36, 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 docs37 for each respective category of data.

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

Completed application38
Our completed application (View large version39)

Final Notes Link

Our application is complete! Our demo (linked to below) is hosted by Aerobatic40, 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 Wookieepedia41 and StarWars.com42. 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.

(ml, al)

Footnotes Link

  1. 1 http://en.wikipedia.org/wiki/Single-page_application
  2. 2 https://www.smashingmagazine.com/2016/08/a-beginners-guide-to-progressive-web-apps/
  3. 3 https://www.smashingmagazine.com/2016/09/the-building-blocks-of-progressive-web-apps/
  4. 4 https://www.smashingmagazine.com/2016/02/building-first-class-app-leverages-website-case-study/
  5. 5 http://stackoverflow.com/questions/671118/what-exactly-is-restful-programming
  6. 6 http://foundation.zurb.com/apps/
  7. 7 https://github.com/spsaucier/ffa_sw
  8. 8 https://github.com/spsaucier/ffa_sw/archive/master.zip
  9. 9 http://foundation.zurb.com/apps/docs/#!/
  10. 10 https://docs.angularjs.org/api
  11. 11 http://swapi.co
  12. 12 http://swapi.co/documentation
  13. 13 http://installrails.com/
  14. 14 https://nodejs.org/
  15. 15 http://foundation.zurb.com/apps/docs/#!/installation
  16. 16 https://www.smashingmagazine.com/wp-content/uploads/2015/03/01-default-page-opt.png
  17. 17 https://www.smashingmagazine.com/wp-content/uploads/2015/03/01-default-page-opt.png
  18. 18 http://gulpjs.com/
  19. 19 https://www.smashingmagazine.com/2014/06/11/building-with-gulp/
  20. 20 http://gruntjs.com/
  21. 21 https://github.com/spsaucier/ffa_sw/blob/master/client/index.html
  22. 22 https://github.com/spsaucier/ffa_sw/blob/master/client/assets/scss/app.scss
  23. 23 https://www.smashingmagazine.com/wp-content/uploads/2015/03/02-template-in-action-opt.png
  24. 24 https://www.smashingmagazine.com/wp-content/uploads/2015/03/02-template-in-action-opt.png
  25. 25 https://docs.angularjs.org/api/ng/filter/filter
  26. 26 https://www.smashingmagazine.com/wp-content/uploads/2015/03/03-linked-list-opt.png
  27. 27 https://www.smashingmagazine.com/wp-content/uploads/2015/03/03-linked-list-opt.png
  28. 28 http://en.wikipedia.org/wiki/Semantic_HTML
  29. 29 https://www.smashingmagazine.com/wp-content/uploads/2015/03/04-characters-opt.png
  30. 30 https://www.smashingmagazine.com/wp-content/uploads/2015/03/04-characters-opt.png
  31. 31 https://docs.angularjs.org/guide/directive
  32. 32 https://github.com/spsaucier/ffa_sw/blob/master/client/templates/films.html
  33. 33 https://www.smashingmagazine.com/wp-content/uploads/2015/03/05-characters-fixed-opt.png
  34. 34 https://www.smashingmagazine.com/wp-content/uploads/2015/03/05-characters-fixed-opt.png
  35. 35 http://swapi.co/documentation
  36. 36 http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
  37. 37 http://swapi.co/documentation
  38. 38 https://www.smashingmagazine.com/wp-content/uploads/2015/03/06-complete-app-opt.png
  39. 39 https://www.smashingmagazine.com/wp-content/uploads/2015/03/06-complete-app-opt.png
  40. 40 http://www.aerobatic.com/
  41. 41 http://starwars.wikia.com/
  42. 42 http://www.starwars.com/
  43. 43 https://github.com/spsaucier/ffa_sw
  44. 44 https://github.com/spsaucier/ffa_sw/archive/master.zip

↑ Back to top Tweet itShare on Facebook

Stephen Saucier is forever torn between Australia & the USA, and he’s currently building web applications for Gather along with freelancing and speaking. He loves good typography, building first-rate applications, and ultimate frisbee.

  1. 1

    Awesome article. The demo doesn’t seem to work for me in Safari but works fine in Chrome.

    3
  2. 4

    Luke Pettway

    April 28, 2015 5:34 pm

    I am so glad you put this out today! I am using right now to build a web app and it has come in handy every step of the way.

    5
  3. 5

    Baltasar Cooke

    April 28, 2015 7:54 pm

    Looks like another poor attempt by Zurb. Bloated [removed]. You’ll never learn.

    -13
  4. 6

    It seems interesting to try something new like this. I’m curious with this new app, and I will try to learn it as soon as possible now. Hope I will make it good with this new app here.
    Thanks for sharing.

    1
  5. 7

    Why not just stick to Web Standards and Progressive Enhancement and build websites that works in every browser on every device?

    I don’t understand.

    0
  6. 8

    Also:
    I just hope no future web developer falls for this nonsense and thinks that you’d need Javascript for ANYTHING this demo does, let alone make this website REQUIRE Javascript to work. Why on earth would you publish and preach something like this, Stephen?

    -13
  7. 9

    (you mixed up “meters” and “centimeters” in your demo)

    0
  8. 10

    Thanks for making this tutorial, it has really helped me get to grips with using Angularjs in Foundation-Apps.

    I’ve been using Foundation 5 for my web apps but wanted to try Foundation-Apps. I’d been banging away at various tutorials around the web but this one definitely got me on the right track. Angular is quite a step-up from Jquery !!!

    btw: Off-canvas is working fine for me on my android devices. I’m sure (hope) the Safari issue will be fixed in the near future. There’s always going to be bugs on cutting-edge stuff like this.

    5
  9. 11

    Nice article :)

    1
  10. 12

    Maybe it’s worth to tell that $state and ui-sref are parts of the “UI Router” Angular Plugin and nothing special to F4A only…

    2
  11. 13

    this is a great article/tutorial, thanks! resources for foundation for apps seem limited.

    1
  12. 14

    Looks pretty amazing, but is it limited to Angular? I’d love to try this on with Flask+Reactjs project I’m working on.

    0
  13. 15

    Hi, thnx for the cool tutorial. P.S. I think that SEO isn’t the problem either now thst Google is crawling AJAX loaded content and with hashbang links loading and redirect in angular and escaped fragment, SEO is all coverd!

    0
  14. 16

    If you are on a Film detail page like films/1 and you click on the menu the ‘Films’ link to get back to the Films
    listing you can’t go back to the listing. Do you have an idea how to make that work?

    0
  15. 17

    Jesse Breuer

    July 29, 2015 3:33 pm

    After making the initial changes in _settings.scss the app crashes until you go to app.scss and change the position of @import “foundation”; to be before @import “settings”; Otherwise you are referencing variables that are not yet defined.

    1
  16. 18

    You shouldn’t use directly $http in controllers. It’s perceived as bad practice. Move that kind of logic to a service or services and then inject them in controllers / directives.

    1
  17. 19

    Hmmm..the angular isn’t working with importing the api information on chrome. could i be doing something wrong? i even tried with the source code and its not working.

    2

↑ Back to top