您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
实例剖析:如何用Foundation For Apps创建完美Web应用
 
作者:Stephen Saucier 来源:Smashing Magazine 发布于: 2015-05-12
  1025  次浏览      33
 

摘要:不久前,Zurb推出了单页面App框架Foundation for Apps,其与Foundation 5密切相关。它试图提供一种开发Web应用既快速又简单的方法,本文通过实战案例,分析了该框架的使用方法,是一份综合性指南。

【编者按】本文是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自定义搜索在Wookieepedia和StarWars.com中抓取每个实体的图像URI。因此,现在每个详细页面上都显示了动态且相关性极高的图像。

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

  • Star Wars Compendium,Foundation for Apps DEMO展示;
  • Star Wars Compendium Using Foundation for Apps,GitHub上;
  • 源文件下载(ZIP)。
  •    
    1025 次浏览       33
     
    相关文章

    手机软件测试用例设计实践
    手机客户端UI测试分析
    iPhone消息推送机制实现与探讨
    Android手机开发(一)
     
    相关文档

    Android_UI官方设计教程
    手机开发平台介绍
    android拍照及上传功能
    Android讲义智能手机开发
    相关课程

    Android高级移动应用程序
    Android系统开发
    Android应用开发
    手机软件测试
    最新活动计划
    嵌入式软件架构设计 12-11[北京]
    LLM大模型与智能体开发实战 12-18[北京]
    嵌入式软件测试 12-25[北京]
    AI原生应用的微服务架构 1-9[北京]
    AI大模型编写高质量代码 1-14[北京]
    需求分析与管理 1-22[北京]

    android人机界面指南
    Android手机开发(一)
    Android手机开发(二)
    Android手机开发(三)
    Android手机开发(四)
    iPhone消息推送机制实现探讨
    手机软件测试用例设计实践
    手机客户端UI测试分析
    手机软件自动化测试研究报告
    更多...   


    Android高级移动应用程序
    Android应用开发
    Android系统开发
    手机软件测试
    嵌入式软件测试
    Android软、硬、云整合


    领先IT公司 android开发平台最佳实践
    北京 Android开发技术进阶
    某新能源领域企业 Android开发技术
    某航天公司 Android、IOS应用软件开发
    阿尔卡特 Linux内核驱动
    艾默生 嵌入式软件架构设计
    西门子 嵌入式架构设计
    更多...