阅读更多
【编者按】本文是Smashing Magazine中《Creating A Complete Web App In Foundation For Apps》一文的简译内容,详细描述了用Foundation For Apps创建完美Web应用“星球大战知识库”的全过程。

Foundation for Apps是Zurb推出的一个新型单页面App框架,与Foundation 5(也称为Foundation for Sites,一个广泛使用的前端框架)密切相关。它围绕AngularJS和弹性网格框架构建而成。它试图让Web应用的开发过程既快速又简单,开发者可以快速开始编写针对应用的独一无二的代码,而非模板文件。

Foundation for Apps于2014年年底发布,尚未被广泛使用,因此有关如何使用这一框架的优质信息源比较少。本文将作为一篇综合指南,对构建一个功能性移动App的全过程进行介绍。文中的技术细节是为客户打造应用程序的基础,本教程也可看作是对AngularJS和单页应用在更广阔范围下如何使用的有力说明。

根据今年后半年即将上映的一部新电影,我们将构建一个“星球大战知识库”。它是一个使用RESTful API、缓存和Foundation for Apps、AngularJS多个特性的响应式Web应用程序。

想要直接跳到干货?
DEMO观看
在GitHub上查看
文件下载
入门指南

快速浏览官方文档,其对设计样式进行了很好解释,但并没有对应用程序功能进行详细说明。将这一卓越的AngularJS文档放在手边,并牢牢记住Foundation for Apps提供了一些标准之外的服务,AngularJS的一些功能可能无法直接使用。同样要注意,AngularJS 和Foundation for Apps与生俱来就不太适用于对SEO有要求的App,因为大部分内容需要通过AJAX加载。

我们的应用程序将从Star Wars API(在SWAPI中)中提取数据。先浏览下SWAPI文档,了解其中的数据和结构。为了简单起见,我们的应用程序将建立在这一结构之上。

首先,我们要安装Foundation for Apps并创建项目。请确保您已经安装了Ruby(OS X已默认安装)和Node.js,然后遵循Foundation for Apps详细文档中的四个步骤。即便你以前没使用过命令行,这个过程也十分简单。当你完成该些步骤后,在浏览器中输入“http://localhost:8080/#!/”,即可看到应用程序的默认主页。




Foundation for Apps的默认主页 (浏览大图


让我们熟悉一下项目里的文件和文件夹。
应用程序根目录中唯一需要我们注意的文件是gulpfile.js,它向Gulp构建过程发送指令,让其为应用启动服务。Gulp是一个构建系统,与Grunt非常相似。稍后,如果我们想添加一些AngularJS模块或插件时,可以通过JavaScript、CSS文件(引用了这些模块或插件)来更新该Gulp文件。

Cient文件夹里能找到其它我们关注的文件夹:
  • clients/assets/js/app.js,应用程序的控制器、指令和自定义过滤器在该文件中;
  • 应用程序的所有SCSS文件都存放在clients/assets/scss里;
  • clients/index.html是应用程序的主页模板;
  • 在clients/templates/中可以找到所有页面的模板,其中大多数还未创建。

构建模板和主页面

让我们开始构建吧!index.html页面无法很好地满足实际应用的需要,所以首先修改该页面。我们针对小尺寸屏幕增加了Off-Canvas菜单、切换按钮及漂亮的淡入淡出效果(利用Foundation for Apps中“Motion UI”的类样式实现)。你可以直接从Github repository中index.html文件里复制这些代码。

在_settings.scss文件中添加一些SCSS变量来设置颜色、字体和断点:
  $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;

现在我们要加点儿料美化一下app.scss。你可以直接参考Github repository中的该文件。

接下来,快速重写默认的home.html文件,该文件在clients/templates/ 目录下,此目录列出了所有要建页面的链接菜单:
---
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>

我们的模板现在看起来已经相当别致——不再是千篇一律的Foundation。



我们的模板——已经有了些看头(浏览大图
创建电影列表

我们正大步向前。在templates文件夹中,为首个子页面创建模板文件:films.html。把下面小段代码复制粘贴到顶端:
---
name: films
url: /films/:id?p=
controller: FilmsCtrl
---

这段代码告诉了我们三件事:

  • Films为该页面名称,在指向该页面的所有链接中可使用;
  • URL将有两个可选参数:id(根据我们的数据生成的影片ID)和p(所在电影列表中的页码数);
  • 我们将使用自定义AngularJS控制器FilmsCtrl,而不是Foundation for Apps自动创建的默认空白控制器。

因为我们要使用自己的控制器,接下来就要在app.js里创建一个(控制器)。浏览下面的控制器,它将被用于电影列表和单个影片页面。你能看到,该控制器保持追踪URL参数,定位我们所在的结果页面,从外部API里获取所需信息(是电影列表还是单个影片的细节,取决于URL参数),并使用$scope变量将这些信息返回到页面视图里。等angular.module声明关闭后,再把它添加到app.js中。
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.


保存完app.js后,你需要通过终端(Control+ C用来取消操作,然后再运行foundation-app watch)重启服务,以保证应用程序包含该新模板(files.html)文件,而该文件中都创建了新控制器(app.js)。

就是这样,我们有了一个功能完整的控制器,它能从外部RESTful API资源中获取数据,将结果缓存至浏览器的session中,并把数据返回到视图页面中。

再次打开films.html,开始构建可以访问的数据视图。首先增加展示电影列表的视图内容。我们可以访问所有添加到$scope变量中的属性,无需加前缀$scope,如本例中的films、prevPage和nextPage。在模板现有内容下添加如下代码:
<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>

非常明显!如果有多页的数据,我们就会得到一个电影名称列表和页码数。但这些数据还没有特别明显的用途,接下来我们把影片名称换成了影片页面所对应的链接。

我们计划将影片ID作为URL的id参数。我们已访问影片的url属性,正好包含了影片ID,即最后一个斜杠前的最后一个参数。然而,我们如何才能从访问的URL中获取唯一的ID?AngularJS通过自定义过滤器使其变得很容易。把{{film.title}}封装在一个链接里,为该连接添加ui-sref属性(用来设置内部链接),其值为包含定制过滤器的film.url数据,示例代码如下:
<a ui-sref="films({ id:( film.url | lastdir ), p:'' })">
  {{film.title | capitalize}}
</a>

现在页面仍处于不可用状态,因为应用无法识别lastdir和capitalize过滤器是什么。我们需要在app.js文件中控制器的后面定义这些过滤器:
.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] : '';
  }
})

搞定!现在我们有了电影列表,每部电影都能链接到各自的电影页面。



电影链表:不知为什么,总对前面部分有些担心(浏览大图)。

然而,点击链接后进入的却是一个空白页,因为films.html还只是个完整的电影信息列表,还没有被构建成可以显示具体电影信息的页面。展示电影细节是我们下一步的工作。

展示一部电影的细节

FilmsCtrl控制器里的$scope.film变量(与$scope['film']相同)中已包含了单个影片页面所需的所有数据。所以,重新使用films.html,并增添另一部分内容,该内容只有在单个file变量被设置时才可见。接着我们会为<dl>中的每组<dt>和<dd>中设置一个键值对(key-value pair)。同时要记住film中某些字段,如characters在数组中有多个值,需通过ng-repeat嵌套一一显示出来。为了把每个演员与该演员的页面连接起来,我们将使用在影片列表中用到的同样方法:用lastdir过滤器,根据他/她/它的ID,链接至每个演员对应的people页面。
<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>

可是,当我们浏览每部电影的条目时,演员列表仅显示了与该演员相关的URL,而不是演员的名字。



这些人物到底是谁?这样可不行(观察大图)。
我们需要用演员名字来替换这些URL文本,但是我们还没有这些关键性数据。或许在我们获取film的URL中会包含演员的名字。我们再次打开app.js,并增加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每次从$http.get返回的数据中返回单一属性,从中我们可以提取出所需要的属性。为了使用该指令,需要把它添加到ng-repeat循环内,就像这样:
<span get-prop prop="'name'" url="character">{{character}}</span>

很好!现在我们有了每个演员的名称及对应页面的链接,而不只是一个不明所以的URL。数据字段的其它数据添加到file页面后,这个页面就算完成了(Github Repository中查看films.html的其余代码)。



这样就好多了(观看大图

为了重用而重构代码

通过SWAPI文档和应用程序其余部分的规划,我们清楚地看到,所有其它页面的控制器与这个极为相似,只是在获取的数据分类上有些许不同。

根据这一想法,我们把file控制器中代码移至称为genericController的功能函数,并放在app.js最后一个右括号的前面。同时还需要用变量multiple(五个实例)来替换字符串里的'films',用single(一个实例)替换'film',因为它们代表了每个页面实体的复数和单数形式。这正好让我们DRY,是一种易于读取和理解、具有可重用性的代码。

现在我们可以利用新的genericController函数(包含变量数据的复数和单数形式)新建并调用FilmsCtrl控制器,像参数那样:
.controller('FilmsCtrl', function($scope, $state, $http){
  $scope = genericController($scope, $state, $http, 'films', 'film');
})

很棒!我们有一个可重用的控制器,它能够提取任何给定页面所需的数据,并在一定的样式呈现出来!现在可以在FilmsCtrl后以相同的方式创建其他页面的控制器。
.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');
})

接下来,采用与创建films.html相同的方式,为planets、species、people、starships和vehicles创建模板HTML文件。创建时要参考SWAPI文档里每类数据中的各个字段。

瞧!现在所有页面都显示出正确的数据并相互关联!



完整的应用程序(观看大图
结束语

应用程序完成了!我们的应用DEMO(下文有链接)由Aerobatic托管,专门针对前端Web应用。在资源库里,你会看到我们增加了一些特殊选项,以利用Aerobatic API网关,该网关设置了一个代理,可以缓存服务器上被请求的API数据。在没有缓存的情况下,该应用即要进行延迟限制又要尽情请求数限制(SWAPI对每个域的请求数量有限制,跟其他APIs一样),因为我们的数据不经常变化,所以在第一次加载之后,服务器缓存使得一切变得非常高速。因为我们限制了onload请求和图像(数量),第一次加载速度较慢也可以接受,而且,在每个加载页面上,为了使应用速度更快,标题菜单会留在该页面上。

在DEMO和资源库里,你能看到我们在详细页面中添加了另一个API调用,它通过Google自定义搜索在WookieepediaStarWars.com中抓取每个实体的图像URI。因此,现在每个详细页面上都显示了动态且相关性极高的图像。

看看下面的演示,或者仔细分析隐藏部分和特定Foundation技巧的源代码,或者把仓库下载下来,然后在本地构建并改进它。

原文链接:Creating A Complete Web App In Foundation For Apps
  • 大小: 10.1 KB
  • 大小: 25.2 KB
  • 大小: 46.2 KB
  • 大小: 82.6 KB
  • 大小: 12.5 KB
  • 大小: 108.7 KB
来自: CSDN
0
0
评论 共 1 条 请登录后发表评论
1 楼 代码阡陌 2015-05-13 17:43
  

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • AWSLambda和AzureFunctions:无服务器应用程序的集成和迁移

    作者:禅与计算机程序设计艺术 ...在过去的几年中,云计算领域快速发展,Amazon Web Services (AWS) 和 Microsoft Azure 是目前主流的两大云服务提供商。而随着无服务器(Serverless)应用的普及和上云迁

  • 地铁译:Spark for python developers ---构建Spark批处理和流处理应用前的数据准备

    使用PySpark和PyData相关库构建应用来分析社交网络中含有Spark的交互信息. 我们从GitHub收集有关Apache Spark的信息, 在Twitter上检查相关的tweets, 使用 Meetup从更广泛的开源社区得到更多Spark 相关感受。 
本...

  • 云原生应用实现规范:初识 Operator | 周末送书

    基于 Kubernetes 平台,我们可以轻松的搭建一些简单的无状态应用,比如对于一些常见的 web apps 或是移动端后台程序,开发者甚至不用十分了解 Kubernetes 就可以利...

  • Kruise Rollout: 让所有应用负载都能使用渐进式交付

    随着 Kubernetes 上面部署的应用日益增多,如何做到业务快速迭代与应用稳定性之间的平衡,是平台建设方必须要解决的问题。Kruise Rollout 是 OpenKruise 在渐进式交付领域的新探索,旨在解决应用交付领域的流量调度...

  • 从微信扔骰子看iOS应用安全与逆向分析

    以微信扔骰子小游戏为例,记录一次完整 iOS 逆向分析的过程。

  • 云计算基础知识及实践指南(附实例代码)

    随着互联网的飞速发展,各种形式的应用软件的流量日益增长,用户的数据也越来越多,处理这些数据所需的时间也越来越长。云计算为解决这一问题提供了一种新方式。它通过将数据、应用程序、资源以及服务都放置在互联网...

  • 【AI大模型应用实战】AI大模型应用架构(ALLMA)白皮书

    随着模型能力的提升和使用成本的降低,基于大模型构建应用将成为主流趋势。然而,应用层能否与大模型高效交互,将成为产品方案探索效率和效果的关键因素。因此,在模型之上的工程架构中,必须构建一套完整的大模型...

  • 架构设计与模式之:容器化与云原生架构设计模式

    本文将从云原生架构和容器技术的角度出发,结合实际应用场景,系统全面剖析容器化及云原生架构的设计模式及优缺点,并为读者提供参考指导。云原生(Cloud Native)的概念源于 Google 在 Kubernetes 上构建的容器编排...

  • 容器编排的未来:探索基于Kubernetes的微服务编排解决方案

    当今的云计算环境下,容器技术正在成为主流,越来越多的公司选择基于容器技术实现应用部署及运行。容器编排技术也逐渐被普遍采用。通过容器编排工具可以将复杂的分布式系统架构部署、管理及扩展起来,从而提供一个高...

  • .net应用程序和工具

    由于新公司对NET 失去了兴趣,Ximian的创始人Miguel de Icaza创办了Xamarin 公司,并将值得关注的.NET 部分带到了他的新公司,开始创建 Android和 ioS可用的.NET项目。添加对程序集的一个引用,以便在自己的代码中...

  • K8s:渐进式入门服务网格 Istio (一)

    分享一些Istio的学习笔记博文内容涉及:istio下载安装一个Demo运行什么是istio,服务网格等概念介绍istio架构组成,应用场景等理解不足小伙伴帮忙指正对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其...

  • 深入分析JavaWeb Item43 -- Struts2开发入门

    一、Struts2概述1、Struts2是什么?Struts2是一个M(模型—域–范围模型)V(View视图)C(控制器)框架(模型2)。框架都是一个半成品。...用的是struts1的名字,但是与sruts1没啥关系,struts2的

  • DBUS学习-DBUS实例

    由 b178903294创建, 最后修改于8月 20, 2019 一、原生DBUS实例 原生dbus客户端代码和服务端代码: client.c折叠源码 #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;...

  • 第 3-5 课:Flutter 调试及 Android 和 iOS 打包

    例如 Web、Android 等等都是支持调试和断点调试的,Flutter 也不例外,支持调试和单元测试功能,还有性能、布局分析器等等。那么这节课就给大家讲解下 Flutter 的调试和测试等功能的使用,配合一些实例进行讲解。...

  • RMI和SWING,Java web start技术的综合应用

    摘 要 波音产品结构管理树是一个基于RMI,SWING和Java web start等多种技术的一个窗口应用程序,现已投入使用。论文第一章对波音产品结构管理树的历史开发过程,开发碰到的难题,和怎样解决这些难题进行了阐述,并且...

  • .NET Core 3.0 正式公布:新特性详细解读

    另外,大家也可以利用 .NET CLI 通过命令行进行桌面应用程序的创建与构建。例如,您可以通过以下命令快速创建一个新的 Windows Forms 应用: dotnet new winforms -o myapp cd myapp dotnet run  您也可以利用...

  • struts2笔记(一):入门实例

    一、准备工作及实例 1.解压struts-2.1.6-all.zip apps目录:struts2自带的例子程序docs目录:官方文档。 lib 目录:存放所有jar文件。 Src 目录:源文件存放地 2.六个基本包 struts2-core-2.1.6.jar ...

  • Spring Cloud 源码分析之OpenFeign

    在今天的内容中,我们需要详细分析OpenFeign它的工作原理及源码,我们继续回到这段代码。 @Slf4j @RestController @RequestMapping("/order") public class OrderController { @Autowired ...

  • 从零到K8s大师:掌握Kubernetes,玩转容器化部署

    Kubernetes,简称K8s,起源于谷歌...2014年,Kubernetes正式对外发布,由Cloud Native Computing Foundation(CNCF)维护,成为云原生计算的重要组成部分。Kubernetes的发展受益于容器技术的兴起,尤其是Docker的流行。

Global site tag (gtag.js) - Google Analytics