首页 前端知识 面向安卓的 HTML5 和 JavaScript 学习手册(四)

面向安卓的 HTML5 和 JavaScript 学习手册(四)

2024-10-13 20:10:40 前端知识 前端哥 1002 599 我要收藏

原文:Learn HTML5 and JavaScript for Android

协议:CC BY-NC-SA 4.0

九、测试和部署您的移动 Web 应用

信不信由你,测试和部署你的移动 web 应用是开发周期中最重要的,但却被忽视的方面之一。

最基本的测试和部署方法是使用 FTP(文件传输协议)客户端将您的移动 web 应用上传到面向公众的 web 服务器。通常情况下,你可以在手机的网络浏览器中测试上传的应用,通过“玩”来确保一切正常。如果有任何问题,那么您在本地机器上进行修改,测试它,然后重新加载更改后的文件。

这适用于小型应用和非常小的团队;然而,随着您的应用和开发团队的成长,彻底测试功能的每个方面,并跟踪代码更改,变得非常耗时。

自然的“快速修复”进展是开始使用文件共享软件或服务,如 AFP(苹果文件共享协议)、Samba 或 Dropbox 来管理组代码,并在开发团队之间共享项目。这最终会变得很麻烦,熟悉这种技术的人都很清楚——文件冲突并不罕见,解决它们极其困难和费力。也没有代码所有权或责备方法。如果开发人员破坏了一部分功能,没有人可以指责,代码需要更长的时间来修复或回滚。

持续集成原则有助于解决这个问题。持续集成包括 SCM(源代码控制管理)来管理团队中的代码、自动化测试、环境测试和自动化部署。这些原则中的一些可能看起来有些陌生,但将在本章中得到全面阐述。没有一个项目或开发团队太大或太小而不能利用持续集成。

本章首先解释这些持续集成元素是什么。然后,您将得到一个实践练习,展示如何创建单元测试,如何使用 SCM 系统 Git,以及如何使用 Capistrano 将您的应用部署到生产服务器上。

源代码管理

源代码控制管理是持续集成的核心。到目前为止,有几种 SCM 实现,包括以下几种:

  • 饭桶
  • SVN(阿帕奇颠覆)
  • 水银的

SCM 提供了一种存储源文件版本的方法。SCM 通过在初始提交/保存时存储原始文件来做到这一点。然后,SCM 在每次后续提交时只存储每个文件之间的变化/差异。这样可以节省磁盘空间和带宽,因为除非文件是新的,否则每次提交时不会保存整个文件。

当您使用 SCM 存储项目时,它们仍然可以从您的计算机上访问,就像任何其他文件一样。不同之处在于,对于基于 SCM 的项目,您可以提交任何文件更改(包括图像、视频等)。)以便在需要时可以对它们进行版本控制和恢复或比较。

要使用 SCM 提交变更,您需要一个 SCM 客户端,比如 Git 或 SVN。配置管理系统通常也会存储额外的文件作为你项目的一部分。在使用过 Git 和 SVN 之后,Git 是我首选的 SCM,因为它在根目录中只存储了一个可以轻松删除的文件夹;相比之下,SVN 会在你的项目的每个文件夹中存储.svn文件夹,这可能会被证明是一个痛苦的删除。

目前有两种类型的供应链管理:集中式和分布式。集中式系统将所有代码存储在中央服务器上。当开发人员提交时,更改在服务器上合并,而不是在开发机器上。

SVN 是一个集中的供应链管理系统。分布式系统没有中央服务器。提交可以在任何开发人员的机器上进行。如果一个开发人员想要共享他或她的代码,另一个开发人员可以将存储库克隆到他们的机器上,这将包含来自原始存储库的每一个变更。

Git 是一个分布式供应链管理系统。通常情况下,Git 存储库会有一个主要的远程存储库,所有的开发人员都可以在其中进行修改。Git 等系统的优势在于,您可以在没有网络连接的情况下处理项目,但仍然可以在处理项目时将更改提交到您的本地存储库。一旦您有了网络访问权,您就可以提取并合并任何变更,测试它们,然后将您合并的变更推送到远程存储库。如果您有一个中央构建系统,这尤其有用。

SCM 还为您提供了在每次提交时存储注释的能力,这样当您浏览项目提交历史时,您可以看到谁提交了对哪些文件的更改以及每次提交背后的原因。像 Git 这样的配置管理系统默认需要提交注释,而 SVN 不需要。Git 在开发社区中非常受欢迎,这也是它成为本章重点的原因。

分支和标记

大多数 SCM 系统遵循分支和标记的方法。Git 中的 master 分支是主要的代码库;这通常是生产就绪的,包含项目代码的最新版本。当需要添加新特性时,通常会从主分支创建一个分支。这些通常都有代号;一些开发团队使用流行卡通节目中的名字,如 Peter、Meg 或 Stewie,而另一些使用行星名,如 Saturn、Jupiter 或 Mars。您可以使用任何您喜欢的命名约定,只要每个分支都有一个唯一的名称。重要的是创建分支时所做的注释。必须明确分支是干什么的,应该包含什么内容。

分支只是主分支的副本或快照,因此任何新特性都不会干扰生产就绪代码。通常,主分支会定期与新分支合并。这是为了确保对主代码库的任何更改都与分支中的功能更改兼容。在新分支中的特性被实现和测试之后,它将与主分支合并,为生产做好准备。

标签只是您希望保留以供将来参考的项目主分支的重要快照。这些通常是项目的重要版本。如果其他开发人员经常在主分支上工作,而您想要进行工作部署,这可能会很有用。

测试

对于新的 web 开发人员来说,测试您的移动 web 应用是开发周期中最容易被忽视的方面之一。大多数新的移动网站开发者将简单地把他们的网站加载到移动设备上,然后玩玩看它是否能工作。随着您添加更多功能,这可能会很费力,而且您真的不知道代码内部发生了什么。随着您开始编写更多面向对象的代码,您开始看到应用的复杂性在增长(以一种有组织的方式)。您可以基于您输入的内容和您期望得到的内容来测试每个代码单元。

例如,在前面的章节中,您提到了在应用中创建模型来存储数据。表示的完整性和应用背后的逻辑实际上依赖于这些模型如何通过 getters、setters 和其他基于模型的方法接受和输出数据。你可以写一套测试,基于每一个代码单元,包括你输入的内容和你期望得到的回报。这种测试方法被称为单元测试。单元测试允许您测试应用中的每个方法。您为项目的每个方面编写的单元测试越多,您就应该对应用的工作越有信心。这就是所谓的代码覆盖率。请记住,单元测试将只覆盖一定比例的代码,您的目标应该是至少 80%的代码覆盖率。

通过创建单元测试,您可以一次运行一系列针对每个目标 web 浏览器的测试。这应该给你信心,你的代码工作正常。这在发布新的浏览器或浏览器版本时尤其有用,因为您可以通过在新浏览器中运行单元测试来确保您的 JavaScript 代码与新浏览器兼容。

部署您的应用

您的应用可以通过多种方式部署。最常见的方法是通过 FTP(文件传输协议)或 SFTP(安全文件传输协议)进行部署。通常情况下,您会有一个存放生产代码的生产服务器,一个测试最新集成代码的开发服务器,以及一个本地开发服务器。您可能还想创建其他环境,例如预生产服务器,它将模拟生产服务器的确切配置,并且您可能想创建一个临时服务器,在将最终代码投入生产之前,客户端或测试人员将在其中测试您的最终代码。

管理所有这些环境及其代码库可能会有问题,并且每次提交到几个环境时手动部署代码更改可能会变得费力,并且容易出现人为错误。您可能还必须为每个环境执行任务,比如 CSS 预编译、JavaScript 和 CSS 缩小和连接,等等。

您可以将大部分工作交给部署应用,比如 Capistrano。Capistrano 将允许您为每个环境编写部署脚本,并使用一个命令将您的应用部署到任何环境。Capistrano 还允许您将任何更改回滚到以前的工作版本,对于每个部署,Capistrano 将存储每个版本的副本,以便您可以随时回滚。

持续集成服务器

将所有这些应用和实践结合在一起的粘合剂是一个良好的持续集成环境。持续集成环境将检测应用代码中的变化,并自动构建和部署它,以及执行其他任务。这意味着您可以专注于开发世界级的 web 应用,而将重复的部署和测试任务留给持续集成服务器。

本书选择的持续集成服务器是 Atlassian 的 Bamboo。选择这个产品是因为它易于安装,有许多插件,易于设置,并且与 Atlassian 的其他流行软件开发工具兼容,如 JIRA、Crucible 和 FishEye。

您的第一个持续集成项目

第一次创建一个持续的集成项目可能是相当费力的。您将首先在 Aptana Studio 3 中创建一个新项目。为此,打开 Aptana Studio,进入文件 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 新建 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 ** Web 项目**。将项目命名为ci

在项目中创建一个名为js的新文件夹。在这个文件夹中,创建两个名为apptests的文件夹。在app文件夹中,通过转到文件 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 新建 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 文件创建一个名为calculator.js的新的空 JavaScript 文件。你不会添加任何东西。TDD(测试驱动开发)声明您必须首先编写您的单元测试,以便它们在您编写代码之前失败。这意味着您所有的预期结果都是在测试中编写的,所以当您编写代码时,您的代码能够满足它们。这是一个很好的练习。当您在单元测试中编写逻辑规则时,如何实现最终代码并不重要,只要输出满足单元测试即可。

这种工作方法确实有助于你写出更简洁的代码,因为你的代码现在只会满足预期的输出。

编写你的第一个单元测试

您可以选择编写自己的单元测试框架,或者使用现成的框架。这本书的选择是 QUnit。它由 jQuery 社区开发,并定期更新。不仅如此,它还可以从浏览器中运行,或者可以使用一个名为 PhantomJS 的程序从命令行运行。

要为计算器设置一个 QUnit 单元测试,首先在包含以下 HTML 的tests文件夹中创建一个名为calculator.html的新文件。

`


      calculator unit tests


      


      


      

calculator unit tests


      


      
      


      

             

    l`

    这段代码代表了在 QUnit 中运行一个基本的空单元测试所需的 HTML。如您所见,它不包含任何特定于移动设备的标记。

    body标签中,您可以看到有几个带有 id 的 HTML 元素。这些在表 9-1 中描述。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    您需要在移动网络浏览器中运行测试。为此,必须首先配置 Aptana,使其内部服务器绑定到计算机的 IP 地址,而不是其本地 IP 地址 127.0.0.1。为此,请转到 Aptana Studio 3 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 首选项外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传Aptana Studio外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传Web 服务器 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 内置。您将看到一个屏幕和下拉菜单,允许您选择内部 web 服务器的 IP 地址应该绑定到什么,如图图 9-1 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-1。 内置服务器偏好

    选择一个与你的电脑的 IP 地址匹配的(通常是最上面的)点击OK,退出 Aptana Studio,并再次启动它以使更改生效。

    Aptana 完成启动后,您可以在 web 浏览器中运行应用,方法是在 Aptana Studio 应用浏览器中右键单击calculator.html,然后选择 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 身份运行 JavaScript Web 应用。这将启动 Firefox。将 Firefox 地址栏中的 URL 输入移动设备的默认浏览器。你应该看到标题栏下面的栏是绿色的,0/0 测试已经运行,如图 9-2 所示。现在是时候开始编写一些单元测试了。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-2。 在浏览器中运行 QUnit 测试

    在 Aptana Studio 中打开calculator.html,如果它还没有打开的话。单元测试只是运行几个断言,检查方法或属性的结果值是否与基于可预测输入的期望值相匹配。本章中的计算器例子很简单,因为很容易预测 1+1 应该总是等于 2。没有其他变量会影响预期结果,所以 2 应该总是预期结果。当结果不是 2 时,您就知道应用在某个地方出了问题。

    calculator.html中,在结束的body标签之前创建一个新的script标签。

    `      …
          


    **       **
    **      

    **      **
      

    `

    script标签中,您可以开始编写您的第一组单元测试。尽管计算器代码还没有写出来,但是您可以开始规定代码中应该存在什么方法和属性,以及它们在单元测试中应该如何表现。一个基本的计算器应该做到以下几点:

    • 增加
    • 减去
    • 划分
    • 乘;成倍增加;(使)繁殖

    计算器通常会采用初始值来执行这些方法,并且应该返回每个方法的结果。你也应该能够清除计算器。在此基础上,应实施以下方法:

    • add
    • subtract
    • divide
    • multiply
    • clear
    • getResult

    然后,您可以将应用的描述转换成单元测试。

    因此,为了创建适当的单元测试,在构造函数上的第一个单元测试的<script />标记中结束下面的代码和注释。

    `test(‘calculator constructor’, function(){

    /**
        * Specify how many assertions this test will run
        * If assertions do not run for any reason, this
        * test will fail
        */
       expect(1);

    /**
        * You create a new calculator instance and set
        * initial value to 10
        */
       var calculator = new app.calculator(10);

    /**
        * You then assert that 10 is being held as the
        * current result
        */
       equal(calculator.getResult(), 10, ‘the result should equal 10 with no
    operation’);

    });`

    如您所见,测试方法是 QUnit 的一部分,并接受描述和回调函数。当测试执行时,回调函数被调用,测试被执行。在测试中,您可以看到几种方法。指定测试中将运行多少个断言。您还实例化了 calculator 类,以便它可以在测试中使用。每个测试创建的任何变量都会被销毁,并且不能在另一个测试中使用。但是,它们可以在测试中的任何断言中使用。最后,equal()是一个断言。断言只是获取一个结果,并检查结果或返回值是否与期望值匹配。在这种情况下,equal断言检查新计算器的初始结果是否为 10。

    这就是单元测试的全部内容。有各种各样的断言可用。参见表 9-2 中的重要列表。

    正如你所看到的,断言往往遵循相同的模式,有一个实际值(通常是属性或方法的直接返回值),和一个期望值(你指定的值和描述断言的消息)。

    断言方法可以改变,所以最好查阅 QUnit 文档,可以在[docs.jquery.com/QUnit](http://www.docs.jquery.com/QUnit)找到。

    如果您现在通过刷新手机浏览器中的网页来运行第一个测试,您可以看到绿色条现在应该是红色的,如图 9-3 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-3。 单元测试失败

    正如您所看到的,失败的测试还会告诉您测试失败的地方和原因,这对调试非常有用。

    有了第一个断言,您现在可以使用接下来的代码来完成计算器其余部分的单元测试。

    `

    test(‘calculator constructor’, function(){

    /**
           * Specify how many assertions this test will run.
           * If assertions do not run for any reason, this
           * test will fail
           */
          expect(1);

    /**
           * Create a new calculator instance and set the initial value to 10 */
          var calculator = new app.calculator(10);

    /**
           * You then assert that 10 is being held as the current result
           */
          equal(calculator.getResult(), 10, ‘the result should equal 10 with no
    operation’);

    });

    test(‘calculator add’, function(){

    /**
           * Specify how many assertions this test will run.
           * If assertions do not run for any reason, this
           * test will fail
           */
          expect(2);

    /**
           * Create a new calculator instance and set the initial value to 10
           */
          var calculator = new app.calculator(10);

    /**
           * Assert that both the return value of the operation and the getResult
           * method both return 20
           */
          equal(calculator.add(10), 20, ‘10 + 10 should equal 20’);
          equal(calculator.getResult(), 20, ‘10 + 10 should result in 20’);

    });

    test(‘calculator subtract’, function(){

    /**
           * Specify how many assertions this test will run.
           * If assertions do not run for any reason, this
           * test will fail
           */
          expect(2);

    /**
           * Create a new calculator instance and set the initial value to 10
           */
          var calculator = new app.calculator(10);

    /**
           * Assert that both the return value of the operation and the getResult * method both return 5
           */
          equal(calculator.subtract(5), 5, ‘10 - 5 should equal 5’);
          equal(calculator.getResult(), 5, ‘10 - 5 should equal 5’);

    });

    test(‘calculator divide’, function(){

    /**
           * Specify how many assertions this test will run.
           * If assertions do not run for any reason, this
           * test will fail
           */
          expect(2);

    /**
           * Create a new calculator instance and set the initial value to 10
           */
          var calculator = new app.calculator(10);

    /**
           * Assert that both the return value of the operation and the getResult
           * method both return 5
           */
          equal(calculator.divide(2), 5, ‘10 / 2 should equal 5’);
          equal(calculator.getResult(), 5, ‘10 / 2 should equal 5’);

    });

    test(‘calculator multiply’, function(){

    /**
           * Specify how many assertions this test will run.
           * If assertions do not run for any reason, this
           * test will fail
           */
          expect(2);

    /**
           * Create a new calculator instance and set the initial value to 10
           */
          var calculator = new app.calculator(10);

    /**
           * Assert that both the return value of the operation and the getResult
           * method both return 20
           */
          equal(calculator.multiply(2), 20, ‘10 * 2 should equal 20’);
          equal(calculator.getResult(), 20, ‘10 * 2 should equal 20’);    });

    `

    如您所见,有许多断言,但这是为了确保涵盖应用的每个方面。您可以在单元测试中进行更深入的研究,比如确保当无效值(比如字母)被传递给方法时抛出错误;然而,这不在本章讨论范围之内。

    单元测试完成后,现在是时候为计算器编写代码了。由于这一节的重点是创建单元测试,所以不会详细解释计算器 JavaScript 代码。代码注释应该有助于解释一下。

    `var app = app || {};

    app.calculator = function(_initialValue){

    /**
        * The current result of the calculator
        */
       var _result = _initialValue;

    /**
        * Gets the current result of the calculator
        */
       this.getResult = function(){
          return _result;
       }

    /**
        * Adds a value to the current result and returns the new value
        */
       this.add = function(value){
          _result = _result + value;
          return _result;
       }

    /**
        * Subtracts a value from the current result and returns the new value
        */
       this.subtract = function(value){
          _result = _result - value;
          return _result;
       }

    /**
        * Multiplies a value from the current result and returns the new value
        */    this.multiply = function(value){
          _result = _result * value;
          return _result;
       }

    /**
        * Divides a value from the current result and returns the new value
        */
       this.divide = function(value){
          _result = _result / value;
       return _result;
       }

    }`

    现在,当您在移动浏览器中运行单元测试时,当您重新加载移动 web 浏览器时,结果栏应该变成绿色,如图 9-4 所示。

    您可以点击任何单元测试结果来查看运行的断言。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-4。 通过单元测试

    单元测试就绪后,现在是时候为您的计算器项目创建一个本地 Git 存储库了。

    使用 Git 和 GitHub

    现在您的项目中已经有了一些文件,是时候创建一个本地存储库了。正如本章前面提到的,Git 是一个分布式配置管理系统。这允许您在没有网络连接的情况下提交到本地存储库,然后将您的更改推送到远程存储库,比如托管在 github.com 上的存储库。

    创建本地存储库很容易;只需点击 Aptana Studio 中应用浏览器上方的命令图标,然后点击初始化 Git 库。这将在后台运行适当的命令git init,以便您可以开始检入文件。

    **注意:**重要的是要记住,Git 作为一个系统,不会将空文件夹提交到存储库中。如果您有一个需要作为项目的一部分签入的空文件夹,则应该在其中创建一个空文件。Git 将获取空文件并签入文件夹。

    初始化 Git 存储库不会自动签入任何新文件。为此,再次点击命令图标。现在 Git 存储库已经初始化,您将在下拉菜单中看到一些新命令。向下滚动到提交菜单项并点击它。将出现一个新窗口,如图图 9-5 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    **图 9-5。**Git 提交窗口

    从图 9-5 中可以看到,有四个主框:提交更改框(顶部)、未登台更改框(左下角)、提交消息框(右下角)和登台更改框(右下角)。“提交更改”框显示了“未暂存的更改”框中所选文件与存储库中当前版本之间的差异。“未转移的更改”框显示自上次提交以来已更改的所有文件。这些文件有三种状态:

    • 白色:新文件
    • 红色:删除的文件
    • 绿色:文件已更改

    “提交”消息框允许您添加提交消息;您需要在每次提交时添加一条提交消息。“阶段更改”框显示了将通过当前提交提交的所有文件。为了提交文件,您必须将它们从“未分段更改”框移到“分段更改”框中。通常情况下,您会希望提交所有文件。点击未分级变更框旁边的>>按钮。这将自动将所有文件移动到“暂存更改”框中。输入提交消息并点击Commit按钮。这将把所有的更改提交给本地存储库。

    当你在 Aptana Studio 中改变文件时,你会看到它们的颜色会在应用浏览器中改变,如图图 9-6 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-6。 改变了应用浏览器中的文件

    文件或文件夹旁边的星号(*)表示更改。根据您的 Aptana Studio 主题,文件的背景也会改变颜色,以指示文件的当前状态。如果您想了解特定主题的颜色含义,请查看 Aptana Studio 首选项窗格中的主题首选项,并查找未登台文件和登台文件元素,如图 9-7 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-7。 检查暂存/未暂存文件的配色方案

    现在,您的文件已经提交到本地存储库,现在是时候将它推送到远程存储库了。

    使用远程存储库总是好的,即使你是自己为一个项目工作。这样做的主要原因是,如果您的计算机发生问题,您不仅可以对您的项目进行持续备份,还可以对您之前的所有提交和更改进行备份。

    本书选择的远程存储库是 GitHub。毫无疑问,它是当今最流行的存储库服务之一,为成百上千的开源开发项目提供免费(但公开)的项目源代码托管空间。

    首先,去 github.com 注册一个名为ci的免费账户和公共存储库。按照说明为本地机器设置 Git SSH 密钥。

    • MAC:??]
    • 视窗:[help.github.com/win-set-up-git/](http://help.github.com/win-set-up-git/)
    • Linux:??]

    在您设置好 SSH 密钥并通过终端登录 github.com 成功测试之后,返回到您在 github.com 的项目页面。你应该会看到类似于图 9-8 的东西。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-8。 默认 GitHub 项目页面

    您可以使用 Aptana Studio 内置的 Git 客户端,而不是使用命令行将您的项目推送到 GitHub。为此,复制项目的 GitHub 远程 URI。该地址将类似于git@github.com:gavinwilliams-fishrod/ci.git。它是从下一步部分的底部数第二个 URI。

    在 Aptana Studio 中,前往命令 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 更多 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **添加遥控…**如图图 9-9 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-9。 添加远程存储库

    会出现一个新的对话框,如图图 9-10 所示。将 GitHub 远程 URI 粘贴到远程 URI 盒中,并按下OK按钮。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-10。 添加 Git 远程对话框

    虽然 Aptana Studio 对它刚刚做的事情不会说得太多,但它刚刚在您的项目中添加了一个远程存储库,别名为origin。当你现在打开命令菜单时,有几个新的活动项,如,如图图 9-11 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-11。 新激活的远程命令

    不幸的是,Aptana Studio 中的 Git 客户端不会自动推送到新的远程存储库。为了解决这个问题,您需要通过项目浏览器进行第一次推送。进入窗口 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 显示视图 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 项目浏览器。右键点击项目浏览器中的ci项目,进入团队 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 推送至远程 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 原点。你的项目现在应该在 GitHub 上,你现在可以使用命令菜单中的命令。重要的是尽可能多的承诺,并在项目工作结束时推动你的改变。

    转到你在 github.com 的项目,你应该看到你的项目已经被推送到 GitHub 了。

    使用 Git 和 GitHub 可以做很多事情,以至于有很多文章和书籍讲述如何真正利用这个系统。您可以在[help.github.com/](http://help.github.com/)了解更多信息。

    既然您已经了解了使用 GitHub 的基本知识,那么是时候了解 Capistrano 了,它是 web 应用的首选部署平台。

    与卡皮斯特拉诺打成一片

    Capistrano 是一个部署平台,有助于消除一些重复的部署任务。对于一个小型的移动 web 应用,Capistrano 可以被看作是用一把大锤在一块木头上钉钉子。随着应用的增长,最终会有更多的环境和细节需要在应用中配置,Capistrano 突然感觉像是一股新鲜空气。

    在这一节中,您将关注使用 Capistrano 将您的应用简单地部署到生产环境中。

    本书的首选主机提供商是 theserve.com,首选服务器操作系统是 CentOS 5;但是,您可以自由选择使用任何提供 SSH 访问的主机。

    按照第一章中的安装指南,你应该已经安装了 Ruby。卡皮斯特拉诺是一种红宝石。要安装它,从应用浏览器进入命令 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 打开终端。终端窗口应该在窗口的右边打开。输入以下命令来安装 Capistrano、Capistrano Rsync With Remote Cache 和 Capistrano multimedia:

    • 视窗:gem install capistrano capistrano_rsync_with_remote_cache capistrano-ext
    • MAC/Linux:??]

    在安装了 Capistrano 和所有需要的 gems 之后,你现在可以开始你的项目了。返回应用浏览器,确保没有项目被选中/高亮显示,然后进入命令 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 打开终端。一个终端窗口将会打开,如图 9-12 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-12。 终端窗口

    这将确保任何运行的命令都将在项目的根目录下运行。为了验证这一点,确保ci显示在命令行的某处,如图 9-12 中的所示。

    为了使用 Capistrano,你需要创建一组配置文件。Capistrano 可以通过一个名为capify的命令行工具自动为您完成这项工作。要完成您的项目,请到您的终端并运行命令capify。您将看到类似于图 9-13 的输出。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-13。 资本化输出

    正如您所看到的,在您的项目中已经创建了几个文件和文件夹。通过单击应用浏览器并按键盘上的 F5 键来刷新应用浏览器,以查看更改。新文件如图 9-14 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-14。 新卡皮斯特拉诺文件

    在配置 capistrano 之前,您应该配置您的生产服务器,以便您可以使用无密码登录将您的 capistrano 项目部署到它上面。作为其中的一部分,您将需要使用在设置 Git 时生成的公共 rsa 密钥。这使得 capistrano 无需干预即可运行。将id_rsa.pub文件的内容复制到您的剪贴板,并使用您的服务器的 SSH 用户名和密码登录到您的新生产服务器。在图 9-15 中显示的命令,从 Aptana 的终端或 Mac OSX 的终端运行应该可以促进这一点。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-15。 在 Mac 上使用 Terminal.app 登录远程服务器

    如果您运行的是 Windows,您可以使用 PuTTy 应用([www.chiark.greenend.org.uk/~sgtatham/putty/](http://www.chiark.greenend.org.uk/~sgtatham/putty/))登录到您的远程服务器。

    您需要使用命令cd ~/切换到当前用户的主目录,如图 9-16 中的所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-16。 切换到主目录

    应该有个文件夹叫.ssh。您可以通过运行命令ls来检查文件夹是否存在。使用命令cd .ssh进入该目录。如果文件夹不存在,需要使用mkdir .ssh命令创建目录,然后使用cd .ssh进入目录,如图图 9-17 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-17。 检查和创建。ssh 目录

    进入目录后,如果还没有一个名为authorized_keys的文件,您需要创建一个新文件。使用ls命令检查文件是否已经存在。

    要创建authorized_keys文件,如果它不存在,只需运行命令touch authorized_keys。这将创建一个空文件,如图图 9-18 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-18。 检查并创建授权密钥文件

    然后,您需要将计算机的公钥添加到authorized_keys文件中。使用vi authorized_keys.编辑文件如果authorized_keys文件中已经有内容,您需要使用箭头键向下移动到它的底部,然后按住 Shift + I (insert)。一旦在底部,将公钥粘贴到文件中,如图图 9-19 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-19。 添加公钥

    您现在需要保存文件。按键盘上的 Esc 键,然后按住 Shift +:。然后键入wq(写退出),并按下键盘上的回车键。你应该会看到类似于图 9-20 中所示的东西。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-20。 在 vi 中写文件

    最后,您需要为新文件设置适当的权限。使用命令cd ../返回主目录。.ssh文件夹需要所有者读/写/执行权限。为此,运行命令chmod 0700 ./.ssh

    接下来,authorized_keys文件需要所有者的读/写权限。为此,运行命令chmod 0600 ./ssh/authorized_keys,如图图 9-21 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-21。 设置权限。ssh 文件夹

    通过在命令行中执行exit命令,注销您的远程服务器。你现在应该可以不用密码重新登录,如图 9-22 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-22。 没有密码的 SSH

    设置了无密码 SSH 之后,现在是时候配置 Capistrano 以部署到生产服务器了。

    回到 Aptana Studio,双击config文件夹中的新deploy.rb文件。这包含项目的部署配置。它是用 Ruby 编写的,但是为了理解和配置它,你不一定要了解很多 Ruby。

    默认的deploy.rb文件是专门为 Ruby 项目设置的,这并不是您想要使用的。首先,删除文件的内容。deploy.rb文件中新的第一行将是应用名。这可以使用下面一行 Ruby 进行配置:

    set :application, "continuousintegration" # The application name

    这暂时没有真正的功能用途,但是您可以在您的配置任务和选项中使用这个变量。

    接下来,您需要使用以下 Ruby 代码包含多阶段和 Capistrano-offroad gems:

    require 'capistrano/ext/multistage' require 'capistrano-offroad'

    这将包括允许您从单个命令行部署和控制多个环境的适当代码。除了提供标准的 web 友好部署配置,Capistrano-offroad 还允许您在 Ruby 项目之外使用 Capistrano。

    接下来,您需要用下面几行代码配置 Capistrano 多级:

    set :stages, %w(production) set :default_stage, "production"

    stages行指出了您希望部署到的环境。您可以拥有任意多个环境,只要它们由空格分隔,如下所示:

    set :stages, %w(production staging development testing preprod)

    stage 环境的名称应该用作配置文件的名称,这一点在本章中有进一步的介绍,您可以随意命名环境,只要它只包含字母字符,不包含空格或特殊字符。如果您从 cap 命令中排除 stage 名称,变量default_stage将设置默认的 stage 环境。

    接下来,指定要用于部署的越野模块。越野默认值模块将覆盖许多默认的 capistrano 挂钩,例如创建共享文件/文件夹定义,Capistrano 将在修订之间共享这些定义,而不是覆盖它们。对于日志、配置选项和用户数据来说,这通常很方便。如果需要,您可以重新实现这些钩子,但是对于没有服务器端代码的应用来说,包含这些钩子是没有意义的。

    offroad_modules 'defaults'

    现在您必须配置 Git。Capistrano 将从 Git/GitHub 中签出最新的指定分支,以便可以上传。:repository变量是项目的 GitHub URI,它将用于从 GitHub 检查最新的代码。

    set :repository,  "git@github.com:gavinwilliams-fishrod/ci.git" # The git repo URI set :scm,         :git # Tells capistrano to use GIT set :branch,      "master"  # Tells capistrano which branch to use

    :scm变量只是告诉 Capistrano that 将被用作 SCM 系统。

    您可以通过修改:branch配置选项来指定使用哪个分支。例如,要使用名为 special 的分支,请使用以下设置:

    set :branch,      "special"  # Tells capistrano which branch to use

    下一个变量将告诉 Capistrano 如何部署应用。在这种情况下,将使用rsync_with_remote_cache。这将克隆 Git 存储库,然后使用 rsync 将其部署到生产服务器。如果您的服务器的防火墙阻止传入的 Git 流量,这将非常方便。

    set :deploy_via,  :rsync_with_remote_cache # Tells capistrano to deploy via rsync

    Rsync 是一个应用,将比较文件夹,并同步它们。您还可以对远程服务器上的远程文件夹使用 rsync。

    在撰写本文时,rsync_with_remote_cache有一个错误,该错误阻止您从带有空格的文件夹中同步。要解决这个问题,您需要使用以下配置变量指定一个替代的临时文件位置:

    set :local_cache, '/tmp/ci/' # The directory where you want to store the rsync cache

    最后,以下 Capistrano 配置选项从其他配置选项中获取变量:

    role(:web) { domain }   # Your HTTP server, Apache/etc role(:app)  { domain }  # This may be the same as your Web` server
    role(:db) { domain }    # This is where Rails migrations will run

    set  :keep_releases,  5 # Tells capistrano how many releases to keep`

    为了给您一个概述,完整的配置文件应该如下所示:

    `set :application, “continuousintegration” # The application name

    require ‘capistrano/ext/multistage’
    require ‘capistrano-offroad’

    set :stages, %w(production)
    set :default_stage, “production”

    offroad_modules ‘defaults’

    set :repository,  “git@github.com:gavinwilliams-fishrod/ci.git” # The location of the
    git repo, this is the read-only url
    set :scm,         :git # Tells capistrano to use GIT
    set :branch,      “master”  # Tells capistrano which branch to use
    set :deploy_via,  :rsync_with_remote_cache # Tells capistrano to deploy via rsync # Or: accurev, bzr, cvs, darcs, subversion, mercurial, perforce,
    subversion or none

    set :local_cache, ‘/tmp/ci/’ # Set this to a directory where you would like to store the
    rsync cache

    role(:web) { domain }   # Your HTTP server, Apache/etc
    role(:app)  { domain }  # This may be the same as your Web server
    role(:db) { domain }    # This is where Rails migrations will run

    set  :keep_releases,  5 # Tells capistrano how many releases to keep`

    如前所述,Capistrano 多级 gem 用于允许您从命令行控制和部署到多个环境。此配置文件仅为生产而设置。在 Aptana Studio 中,进入应用浏览器,在config文件夹中创建一个名为deploy的新文件夹。在deploy文件夹中,新建一个名为production.rb的文件,如图图 9-23 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-23。 生产阶段配置文件

    在 Aptana Studio 中打开production.rb文件,添加以下配置选项:

    set :domain,      "ci.fishrod.co.uk" # The domain name of the application

    前面一行代码设置了应用的域名,它将用于登录服务器。

    set :deploy_to,   "~/application/" # The path to deploy the application

    :deploy_to设置应用应部署到的远程服务器上的路径。因为这个实例中 SSH 用户的 web 路径位于用户的主目录中,所以使用~作为参数。如果您的 web 路径在其他地方,您应该使用该路径。比如在大多数空白服务器上,它会存在于/var/www/application/下。首先,您需要在服务器上创建应用文件夹。

    set :user,        "ci.fishrod.co.uk" # The SSH user for your website set :deploy_group, "ci.fishrod.co.uk" set :use_sudo,    false # Tells capistrano not to run commands as root

    :user将设置用于登录您的远程服务器的用户。:deploy_group将设置 Capistrano 将任何上传文件的权限设置到哪个组。:use_sudo将停止 Capistrano 作为根用户上传和更改文件。

    Capistrano 完全配置好之后,现在是时候为部署设置您的生产服务器了。

    为此,请再次登录到您的远程服务器,然后转到您的 web 服务器的文档根文件夹上的文件夹。例如,您的根目录可能是/var/www/html,所以您应该转到您的/var/www目录。如果你在共享主机上,你应该去~/,那将是你的主目录。

    在该目录下新建一个名为application的目录,并删除你的文档根文件夹,如图 9-24 所示。您的文档根文件夹可能被称为htdocshtmlpublic_html;在本章中,它将被称为public_html

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-24。 创建应用目录,删除文件根目录

    在完成 Capistrano 配置之前,您需要在远程服务器上设置 Capistrano 文件夹。为此,请返回 Aptana Studio 并打开终端视图。运行以下命令:

    cap production deploy:setup

    这将登录到您的远程服务器,并为您创建适当的文件和文件夹。您应该会看到类似于图 9-25 中的输出。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-25。??【卡皮斯特拉诺】部署:设置输出

    现在您可以部署您的应用了。运行以下命令可以做到这一点:

    cap production deploy

    如果一切顺利,您将看到类似于图 9-26 所示的最终输出。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-26。 最终 Capistrano 部署输出

    最后要做的是在 web 根目录和当前版本之间创建一个符号链接。这允许您在服务器上保留代码的修订版,如果在部署过程中出现任何问题,Capistrano 可以回滚。

    为此,返回到连接到您的服务器的终端窗口(用于创建应用文件夹和删除public_html文件夹的窗口)并运行以下命令:

    ln -s application/current/ public_html

    运行ls命令,验证符号链接已经创建,如图图 9-27 所示。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    图 9-27。 新建公共 _html 符号链接

    现在,使用以下 URL 在远程服务器上导航到您的单元测试,当然,用您自己的域替换*yourdomain*:[yourdomain/js/tests/calculator.html](http://yourdomain/js/tests/calculator.html)

    如您所见,Capistrano 是一个强大的工具。它不仅可以用于进行部署,还可以用于在远程服务器上运行命令,而不必直接登录到它们。这对于重新编译 SASS 文件或者连接和缩小 JavaScript 文件非常有用。Capistrano 更好的一点是,因为它是从命令行运行的,所以可以集成到 Hudson 或 Bamboo 等持续集成服务器中。

    总结

    从这一章开始,你应该对持续集成有一个基本的了解,以及它如何影响你测试和部署你的应用。虽然这本书没有涵盖如何实现持续集成服务器,但这是一个值得研究的课题,即使是作为一个单独的开发人员。当您有最后一分钟的代码更改时,您可以确信当您签入代码时,一旦它到达生产环境,它已经被完全测试。您还可以相信,由于自动化,如果它第一次成功部署,它应该会一次又一次地成功部署,除非您破坏了应用的某些元素。在这种情况下,您失败的测试将指出哪段代码没有按预期工作。

    仅通过这一章,你就应该对如何使用 QUnit 创建 JavaScript 单元测试有了一个真正的立足点。还有其他更先进的测试产品,如 Test Swarm ( [github.com/jquery/testswarm](https://github.com/jquery/testswarm))、Jasmine([pivotal.github.com/jasmine/](http://pivotal.github.com/jasmine/))和 Selenium ( [seleniumhq.org](http://seleniumhq.org)[code.google.com/p/selenium/wiki/AndroidDriver](http://code.google.com/p/selenium/wiki/AndroidDriver))。重要的是要记住,尽管 TDD 起初看起来像是一项费力的任务,但它确实允许您思考您的代码及其构造方式,这有助于产生更干净、更精简的 JavaScript。

    您应该对如何通过 Aptana Studio 使用 Git 和 GitHub 有一个基本的了解,以及它不仅对您一个单独的开发人员有好处,对最终与您一起工作的其他人也有好处。

    最重要的是,您现在应该知道如何设置 Capistrano,这是一个强大的部署应用,主要用于部署 rails 应用。值得看一看 Capistrano 文档,以探索它的所有功能。在[github.com/capistrano/capistrano/wiki/](https://github.com/capistrano/capistrano/wiki/)找到它。

    这本书应该已经让你走上了移动网络开发的正确道路。有些主题可能看起来有点超前;然而,我觉得,随着行业发展如此之快,保持领先地位并尽可能多地挑战自己总是很重要的。希望你从这本书中学到的一些实践和原则能让你对移动网络产生兴趣。现在,您应该已经掌握了为 Android 构建相当先进的移动 web 应用所需的所有知识。

    十、附录 A

    A-1 清单

    `var app = app || {};

    app.model = app.model || {};

    /**
     * A movie model used for all movies within the application
     *
     * @alias app.model.movie
     * @constructor
     * @param {String} title
     * @param {String} rtid
     * @param {String} posterframe
     * @param {String} synopsis
     */
    app.model.movie = function appModelMovie(title, rtid, posterframe, synopsis) {

    /**
        * The video’s instance variables
        /
       var _title,
          _rtid,
          _posterframe,
          _synopsis,
          _releaseDate,
          _videos = [],
          _actors = [],
          _rating,
          _favorite = false,
          _self = this; /
    *
        * Getters and setters
        */

    this.init = function(){
          /**
           * Set the instance variables using the constructor’s arguments
           */
          this.setTitle(title);
          this.setRtid(rtid);
          this.setPosterframe(posterframe);
          this.setSynopsis(synopsis);
       }

    /**
        * Returns the movie title
        * @return {String}
        */
       this.getTitle = function(){
          return _title;
       }

    /**
        * Sets the movie title
        * @param {String} title
        */
       this.setTitle = function(title){
          _title = title;
       }

    /**
        * Returns the Rotten Tomatoes reference ID
        * @return {String}
        */
       this.getRtid = function(){
          return _rtid;
       }

    /**
        * Sets the Rotten Tomatoes reference ID
        * @param {String} rtid
        */
       this.setRtid = function(rtid){
          _rtid = rtid;
       }

    /**
        * Gets the posterframe URL/Path
        * @return {String}
        */ this.getPosterframe = function(){
          return _posterframe;
       }

    /**
        * Sets the posterframe URN/Path
        * @param {String} posterframe
        */
       this.setPosterframe = function(posterframe){
          _posterframe = posterframe;
       }

    /**
        * Gets the synopsis as a string with no HTML formatting
        * @return {String}
        */
       this.getSynopsis = function(){
          return _synopsis;
       }

    /**
        * Sets the synopsis, a string with no HTML must be passed
        * @param {String} synopsis
        */
       this.setSynopsis = function(synopsis){
          _synopsis = synopsis;
       }

    /**
        * Gets all videos associated with the movie
        * @return {Array}
        */
       this.getVideos = function(){
          return _videos;
       }

    /**
        * Sets all videos associated with the movie
        * @param {Array}
        */
       this.setVideos = function(videos){

    _videos.length = 0;

    /**
           * Rather than setting the videos all in one go,
           * you use the addVideo method, which can handle
           * any validation for each video before it’s
           * added to the object
           */ for(var i = 0; i < videos.length; i++){
             _self.addVideo(videos[i]);
          }
       }

    /**
        * Adds a video to the movie
        * @param {app.model.video} video
        /
       this.addVideo = function(video){
          /
    *
           * You can add any video validation here
           * before it’s added to the movie
           */
          _videos.push(video);
       }

    /**
        * Gets all actors associated with the movie
        * @return {Array}
        */
       this.getActors = function(){
          return _actors;
       }

    /**
        * Gets an actor at a specific index
        * @param {Integer} index
        * @return {app.model.actor}
        */
       this.getActor = function(index){
          return _actors[index];
       }

    /**
        * Sets all actors associated with the movie
        * @param {Array}
        */
       this.setActors = function(actors){

    _actors.length = 0;

    /**
           * Rather than setting the actors all in one go,
           * you use the addActor method, which can handle
           * any validation for each actor before it’s
           * added to the object
           */
          for(var i = 0; i < actors.length; i++){
             _self.addActor(actors[i]); }
       }

    /**
        * Adds an actor to the movie
        * @param {app.model.actor} actor
        /
       this.addActor = function(actor){
          /
    *
           * You can add any actor validation here
           * before it’s added to the movie
           */
          _actors.push(actor);
       }

    /**
        * Sets the release date
        */
       this.setReleaseDate = function(releaseDate){
          _releaseDate = releaseDate;
       }

    /**
        * Gets the release date
        * @return {app.type.releaseDate}
        */
       this.getReleaseDate = function(){
          return _releaseDate;
       }

    /**
        * Gets the movie rating
        * @return {String}
        */
       this.getRating = function(){
          return _rating;
       }

    /**
        * Sets the movie rating
        * @param {String} rating
        */
       this.setRating = function(rating){
          _rating = rating;
       }

    /**
        * Checks to see whether the movie
        * is in the user’s favorites list
        * @return {Bool}     */
       this.isFavorite = function(){
          return _favorite;
       }

    /**
        * Sets whether the movie is in the
        * user’s favorites list
        * @param {Bool} value
        */
       this.setFavorite = function(value){
          _favorite = value;
       }

    this.init();

    }`

    清单 A-2

    `.footer {
       height: 40px;
       width: 100%;
       text-align: center;
       position: absolute;
       bottom: 0;

    .back {
          height: 100%;
          display: block;
          background: url(‘…/img/back.png’) no-repeat 10px 50%;
          text-indent: -10000px;
       }

    }`

    最终的 SASS 文件应该类似于下面的代码。

    `body, html, #shoe, .deck {
       height: 100%;
       width: 100%;
       overflow: hidden;
       margin: 0px;
    }

    /**
     * Individual Card Styles
     */ #card-movie_search_results {
      z-index: 50;
    }

    /**
     * Deck styles
     */

    .deck {

    position: relative;

    .card {
          height: 100%;
          width: 100%;
          left: -100%;
          position: absolute;
       }

    .card.active {
          left: 0px;
       }

    }

    /**
     * List styles
     */

    /**
     * Standard list
     */

    .list {

    margin: 0;
       padding: 0;

    li {

    padding: 10px;
          overflow: hidden;
          height: 82px;
          display: block;
          border-bottom: 1px solid #CCCCCC;

    .preview-image {
             float: left; width: 60px;
             height: 82px;
             text-align: center;
             margin-right: 10px;
          }

    }

    }

    /**
     * Movie list
     */

    .movie-list {

    li {

    background: #A5CCEB;
          border-bottom-color: #FFFFFF;

    .more {

    display: block;
             height: 100%;
             overflow: hidden;
             text-decoration: none;

    h2 {
                margin: 0 0 10px;
                color: #BF2628;
             }

    p {
                margin: 0;
                color: #000000;
             }

    }

    }

    li:nth-child(odd) {
          background: #97B2D9;
       }

    }

    /** * Header taskbar styles
     */

    .screenbar {
       @include gradient(#7D9DCE, #ABC1E1, 90deg);
    }

    header#taskbar {
       color: #FFFFFF;
       overflow: hidden;
       padding: 10px;
       border-bottom: 1px solid #BF2628;

    h1.branding {
          margin: 0px;
          float: left;
          width: 73px;
          height: 32px;
          text-indent: -10000px;
          overflow: hidden;
          background: url(‘…/img/momemo.png’) no-repeat top left;
       }

    .clear-search {
          float: right;
          width: 35px;
          height: 35px;
          display: none;
          overflow: hidden;
          text-indent: -10000px;
          background: url(‘…/img/clear.png’) 50% 50% no-repeat;
       }

    }

    header#taskbar.searchactive {

    .clear-search {
          display: block;
       }

    form#add-movie {
          margin-right: 40px;
       }

    }

    /**
     * Movie view
     / /*
     * Animations for the poster header
     */
    @keyframes posteranimation {
       0% { top: 0%; }
       100% { top: -80%; }
    }

    @-moz-keyframes posteranimation {
       0% { top: 0%; }
       100% { top: -80%; }
    }

    @-webkit-keyframes posteranimation {
       0% { top: 0%; }
       100% { top: -80%; }
    }

    .movie-header {

    position: relative;
       overflow: hidden;
       height: 20%;

    .poster {
          position: absolute;
          top: 0%;
          @include animation(posteranimation 10s ease 0 infinite alternate);
       }

    .movie-title {
          position: absolute;
          bottom: 0px;
          background: rgba(255, 255, 255, 0.75);
          padding: 5px;
          bottom: 0;
          left: 0;
          width: 100%;
          @include box-sizing(border-box);

    .btn-favorite {
             float: right;
             padding: 10px;
             color: #FFFFFF;
             background: #7D9DCE;
             font-weight: bold; border-radius: 5px;
             text-decoration: none;
             border: 1px solid #A5CCEB;
          }

    .movie-release-date {
             text-transform: uppercase;
             font-weight: bold;
          }

    }

    }

    .movie-content {
       height: 80%;
       width: 100%;
       padding-bottom: 40px;
       @include box-sizing(border-box);

    .block-container {

    width: 280%;
          height: 100%;

    .block {
             width: 33%;
             float: left;
             height: 100%;

    font-size: 1.3em;
             line-height: 2em;

    .content {
                @include box-sizing(border-box);
             }

    h3 {
                padding: 10px 10px 0 10px;
             }

    .content {
                padding: 10px;
             }

    }

    }

    } .footer {
       height: 40px;
       width: 100%;
       text-align: center;
       position: absolute;
       bottom: 0;

    .back {
          height: 100%;
          display: block;
          background: url(‘…/img/back.png’) no-repeat 10px 50%;
          text-indent: -10000px;
       }

    }`

    转载请注明出处或者链接地址:https://www.qianduange.cn//article/18942.html
    标签
    评论
    发布的文章

    JSON:API Normalizer 项目教程

    2024-10-30 21:10:43

    json2html 项目教程

    2024-10-30 21:10:41

    大家推荐的文章
    会员中心 联系我 留言建议 回顶部
    复制成功!