zoukankan      html  css  js  c++  java
  • PartyBid 学习笔记 之 第一张卡片总结

    此博客已弃,请转至 此处

    本文仅为培训期间应试作文,不具任何教学价值,具体问题请参考对应文章。

    AngularJS LOGO


    前情提要

    Party Bid 是一款基于 AngularJS 的安卓网页应用。所谓安卓网页应用,指的是应用完全使用网页开发模式构造(HTML + CSS + JavaScript),之后使用 Apache Cordova 工具将其生成为安卓本地应用项目。

    对于应用内容的介绍,考虑到本文的面向读者,此处不再详细说明,主要内容在于 开发过程中所用到的技术个人学习的一些心得体会


    建立项目

    为了方便的直接建立出 AngularJS 项目,我们需要使用到 Yeoman 工具。

    安装 Yeoman 的步骤已在 [OSX 之 Web 开发环境配置]({% post_url 2014-07-17-OSX之Web开发环境配置 %})文章中给出。

    核心步骤的命令行代码如下:

    $ npm install -g yo
    

    至此已经安装好了 Yeoman ,随后我们通过其来创建一个 AngularJS 项目:

    详细说明及内容拓展请参见 Yeoman 之 搭建 AngularJS 开发环境(待写)

    1.安装 Yoeman 的 AngularJS 模板生成器:

    $ npm install -g generator-angular
    

    含义顾名思义。Yo (Yeoman 的一个组件,下文中都会具体说明每个操作是 Yo, Grunt 或 Bower 的功能)可以通过安装相应的 generator 来实现功能拓展,除了 AngularJS 的之外,还有很多其他的 generator 可以使用。也就是说,通过拓展,Yo 理论上可以适用于任何类型的项目。

    2.新建一个文件夹并进入,文件夹名称即为项目名 party_bid(为了避免各种地方都加引号而抛弃空格),手动或者命令行如下:

    $ mkdir party_bid
    $ cd party_bid
    

    3.使用 Yeoman 创建一个 AngularJS 项目:

    $ yo angular
    

    之后会有提示如下:

         _-----_
        |       |    .--------------------------.
        |--(o)--|    |    Welcome to Yeoman,    |
       `---------´   |   ladies and gentlemen!  |
        ( _´U`_ )    '--------------------------'
        /___A___    
         |  ~  |     
       __'.___.'__   
     ´   `  |° ´ Y ` 
    
    Out of the box I include Bootstrap and some AngularJS recommended modules.
    
    [?] Would you like to use Sass (with Compass)? (Y/n) 
    [?] Would you like to include Bootstrap? (Y/n)
    [?] Would you like to use the Sass version of Bootstrap? (Y/n) 
    [?] Which modules would you like to include? (Press <space> to select)
    ❯⬢ angular-animate.js
     ⬢ angular-cookies.js
     ⬢ angular-resource.js
     ⬢ angular-route.js
     ⬢ angular-sanitize.js
     ⬢ angular-touch.js
    

    上面列出了是否需要在 AngularJS 直接添加一些拓展组建,在此全选,主要理由如下:

    • 由于目前还不是对 Sass 和 Bootstrap 特别了解,以优先保证可用性为前提;
    • Party Bid 为开发项目而不是真正的商业 Web App ,不需要对应用大小最简化来保证用户体验;
    • 即便现在的需求中用不到 Sass ,但在 Sass 本身也能使用纯 css ,之后也可以需要对应用进行 Sass 重构。

    全部选择后结果显示如下:

    [?] Would you like to use Sass (with Compass)? Yes
    [?] Would you like to include Bootstrap? Yes
    [?] Would you like to use the Sass version of Bootstrap? Yes
    [?] Which modules would you like to include? angular-animate.js, angular-cookies.js, angular-resource.js, angular-route.js, angular-sanitize.js, angular-touch.js
    

    注:上述代码可能会随着平台差异和程序版本而不同,请以自己的实际情况为准。

    接下来可能生成的过程会比较漫长,在这段休息时间中可以看看本博客的其他内容 o

    在 AngularJS 项目生成完毕后(要确定终端中是生成完毕停止而不是报错停止的 =_=||),就会发现当前目录中多了很多文件和文件夹:

    • app -- 应用的主文件夹,写代码的地方
    • test -- 放置测试文件的文件夹
    • node_modules -- Nodejs 安装的程序包
    • bower.json -- Bower 的依赖配置文件
    • Gruntfile.js -- Grunt 的配置文件
    • package.json -- NodeJS 的依赖配置文件

    4.在终端中开启 Grunt 的内置服务器:

    $ grunt serve
    

    注意:grunt servegrunt server 都能开启服务器,但是 grunt server 已经不被推荐使用。目前的情况是使用 grunt server 会被重定向到 grunt serve ,所以功能上没有任何区别。

    接着 grunt 会自动在浏览器中打开 app 文件夹中的 index.html 页面,如果能够正常现实内容及样式的话说明我们这个环节已经成功了。


    配置项目

    虽然已经建立了一个 AngularJS 的模板项目,但是我们的命令行操作还并没有结束。

    或者说,Yo 的强大功能还远不仅仅如此。本文仅讲解所用到的功能,拓展内容具体可参考 Yeoman 之 搭建 AngularJS 开发环境(待写)

    AngularJS 中,路由是一个核心功能,用来响应 url 并连接对应的 view 和 controller。关于 MVC 的更多内容可以参考 AngularJS 之 MVC 架构开发介绍(待写)

    在有 Yo 的情况下,我们无需自己添加每一个 view 和 controller 。在第一张卡片中,我们需要用到三个页面:"活动列表" , "活动报名" 和 "创建活动" ,为此,我们在终端中继续输入如下命令:(因为当前正在运行服务器,故可以使用 "Command + N"(Mac)或 "Ctrl + N" 来创建一个新窗口,并再次回到当前文件夹)

    $ yo angular:route ActivityList
    $ yo angular:route ActivityRegister
    $ yo angular:route CreateActivity
    

    这样就直接创建了 app/scripts/controllers/ 文件夹下的 activitylist.js , activityregister.jscreateactivity.js 三个 controller 文件,以及 app/views/ 下的 activitylist.html , activityregister.htmlcreateactivity.html 三个 view 文件,并且已经在 app/scripts/ 下的 app.js 中将模板和控制器相互关联起来了,十分简单粗暴。

    不论在上面的命令中使用 PascalCase 还是 camelCase ,文件名都会自动使用小写,但是 controller 的名称和路由中配置的路径会按照上面命令中给出的大小写来生成。

    为了代码的可读性和可维护性,我们再新建一个 app/scripts/models 文件夹,并在其中添加一个 activity.js 文件用于处理一切与 activity 相关的数据操作。

    特别注意,自己添加文件后也要在 index.html 中添加相应的引用。

    ...
    <#script src='scripts/models/activity.js'></#script>
    ...
    

    博文中禁止 script 标签,请自行去掉#号。

    引用添加的位置不要在它默认生成的脚本块中,放在它的注释块外面。另外 activity.js 中的代码模式为:

    function Activity(parameter) {
      //Do something
    }
    
    Activity.prototype.someMethod = function () {
      //Do something
    };
    
    Activity.anotherMethod = function () {
      //Do something
    };
    

    上面的代码中,采用面向对象的方式,依次是 类的构造函数类的实例函数 以及 类函数


    数据结构设计

    首先分析数据结构,为了保证良好的可读性和可拓展性,采用 对象数组 的形式来在 locolStorage 中存储活动。如果之后改用数据库存储的话,这样也是最方便对接的。

    activity.js 的主要代码如下:

    1.构造函数
    function Activity(name, createdAt) {
        this.name = name;
        this.createdAt = createdAt;
    }
    

    在上面的代码中, Activity 类的对象具有两个字段(可能有些语言一般用属性,效果都是差不多的),其中,createdAt 用于记录活动创建时间,以便于排序。

    注意:因为采用对象数组,虽然其本身的物理存储数据已经可以实现对活动的排序,但是物理存储顺序是绝对不足以作为排序依据的,其具有非常明显的不可靠性。即便不存储创建时间,也需要指定一个 index 属性来支持排序功能。除此之外,也能使用哈希表来作为数据结构,以 index 作为索引,这里不作过多介绍。

    2.读取函数

    此博客已弃,请转至 此处

    因为不针对任何实例,所以此处采用类函数(方法)实现,从 localStorage 中取出相应 json 数组并解析为对象。

    Activity.all = function () {
        return JSON.parse(localStorage.getItem('activities')) || [];
    };
    

    上面的代码中,最后的逻辑或操作是为了防止没有任何数据时导致程序崩溃,因此返回一个空数组而不是 undefined 。而当非空时,由于逻辑运算的短路法则,或运算及其后内容会直接被忽略。

    3.存储函数

    为了保持面向对象的纯洁性,此处采用实例函数而非类函数来进行存储,存储在浏览器本地的 localStorage 中。

    Activity.prototype.save = function () {
        var list = Activity.all();
        list.push(this);
        localStorage.setItem('activities', list);
    };
    

    无活动时跳转

    在卡片一中,存在如下需求:

    在打开程序后判断一下,是否已经存在已创建的活动,如果没有,就要显示“创建活动”页面,引导用户去创建一个活动。

    所以,我们需要在 controller 中增加相应的判断。

    首先,我们来看看现在的路由配置,打开 app.js,部分代码如下:

    angular
      .module('partyBidApp', [
      ...
      ])
      .config(function ($routeProvider) {
        $routeProvider
          .when('/', {
            templateUrl: 'views/main.html',
            controller: 'MainCtrl'
          })
          ...
          .when('/ActivityList', {
            templateUrl: 'views/activitylist.html',
            controller: 'ActivitylistCtrl'
          })
          .when('/CreateActivity', {
            templateUrl: 'views/createactivity.html',
            controller: 'CreateactivityCtrl'
          })
          .otherwise({
            redirectTo: '/'
          });
      });
    

    可以看出,目前的默认页面为 views/main.html,对应的控制器为 MainCtrl

    为此,我们需要在 activity.jsmain.js 中添加一些代码:

    activity.js:

    Activity.exist = function () {
        return (Activity.all()).length == 0;
    };
    

    在 model 中增加这个函数用于判断活动列表是否为空。

    main.js:

    angular.module('partyBidApp')
      .controller('MainCtrl', function ($scope, $location) {
        $scope.initiate = function () {
          var path = Activity.exist()? '/ActivityList': '/CreateActivity';
          $location.path(path);
        };
        
        $scope.initiate();
      });
    

    上面的 controller 中的代码,虽然只有 2 行(甚至可以一行解决),但还是定义了一个 初始化函数 并调用。这是一种习惯,至于是不是好习惯现在还不能确定,只是随着代码量的增加我觉得这种方式能够使代码变得更加 neat 。另一个好处,现在还不能确定是否有用,就是在页面需要更新数据的时候可以直接(被)调用。

    特别注意要在控制器的定义函数参数中添加 $location ,当然,实际上除了 $location.path() 外,还有别的页面跳转方法,比如最简单的 href 或者 AngularJS 的 ng-href 等,此处不做介绍。

    接着运行 grunt serve ,就能看到现在已经跳到了活动报名页面,虽然目前整个页面上就只有一行 "This is the CreateActivity view."


    无活动时返回按钮不显示

    在卡片一中,存在如下需求:

    无已创建活动情况下,进入”创建活动“页面,”返回“按钮不显示。

    AngularJS 中有一个便捷的设置 DOM 可见性的方式,ng-showng-hide ,用法上没有任何区别,只是效果相反。

    注意:所有 ng-someThing 属性都是 AngularJS 中的指令(directive),如果继续深入学习的话读者可能之后会创建自己的指令。

    首先要在 views/createactivity.html 中添加一个按钮,位置等样式以及 ng-click 事件由读者自行实现:

    ...
    <button class='' ng-show='{ {activity_exist} }' ng-click=''>返回</button>
    ...
    

    其中, { { } }(双层花括号,无空格,因格式问题无法直接打出。)是 AngularJS 的数据绑定语法,表示其值绑定到了 $scope.activity_exist 变量上。

    为此,我们需要在 CreateactivityCtrl 中对其进行赋值(仅给出核心代码,下同):

    ...
    $scope.activity_exist = !Activity.exist();
    ...
    

    运行应用,可以看到按钮不显示(这也叫可以看到么 -.-),因为我们还没有添加创建活动的功能,所以目前看不到按钮显示。

    事实上在开发中,我们并不需要按照完整的流程进行测试,在 Jasmine 测试当中,可以直接使用 SpyOn() 来伪造类或对象。这里我们可以先手动创建活动:

    ...
    var new_activity = new Activity('活动一', Date.parse(new Date()));
    new_activity.save();
    ...
    

    将其添加到上面的判断代码之前即可手动创建活动,注意多次运行后将会有多个 活动一 。可在创建一次后删除该代码并重新运行,该活动将保留在 localStorage 中。

    关于 Jasmine 测试的使用方法请参见:[2014-08-08-JavaScript之TDD开发简介](% post_url 2014-08-08-JavaScript之TDD开发简介 %})。

    为了将按钮固定显示在左上角,读者应自行参看模板应用的 css 代码并对此应用进行相应的修改,本文不对 css 样式本身作过多讲解。


    活动列表按时间排序

    在卡片一中,存在如下需求:

    打开程序后直接进入"活动列表"页面,列表显示为已创建活动。
    "活动列表"页面按照时间顺序由新到旧排列活动,最后创建的活动显示在列表的最前。
    点击活动列表中的“活动”查看活动信息。

    这里明显一定要用到动态的数据绑定,当然这也是 AngularJS 最擅长的地方之一。

    和之前的 ng-show 以及 ng-hide 相似,可以使用 ng-repeat 来绑定数组数据。

    为此,在 activitylist.html 中添加一个列表控件(写 Xaml 的时候习惯把页面的东东都叫控件了,见谅),主要代码如下:

    ...
    <ul class=''>
      <li ng-repeat='activity in activities | orderBy:"-createAt"'>
        <a ng-click='go_to_detail(activity.name)'>
          { {activity.name} }
        </a>
      </li>
    </ul>
    ...
    

    上面的代码中,ng-repeat 中的 activity in activities<li> 列表绑定到了 $scope.activities 数组变量中,对于其中的每个元素生成一个 <li> ,并将其名称作为显示内容,每个活动的点击事件调用 $scope.go_to_detail 函数并将活动名称作为参数。 orderBy:"-createAt" 表示以每个活动的 createAt 属性作为排序依据,- 号表示由大到小。

    对于 ng-repeat 中的 ng-click 事件,为了确定具体点击的内容,可以将当前元素的某个属性或者当前元素本身作为参数传递,另一种方法是将 $index 作为参数传递,其代表 ng-repeat 中该元素的位置(从0开始)。

    与此对应的 ActivitylistCtrl 核心代码如下:

    初始化活动列表:

    ...
    $scope.activities = Activity.all();
    ...
    

    活动名称点击事件:

    ...
    $scope.go_to_detail = function (activity_name) {
      $location.path('/ActivityRegister/' + activity_name);
    };
    ...
    

    按照上面的函数代码运行,点击活动,然后,先自己试一试,真的试了么?好吧我相信你。

    页面并没有发生任何变化。Why?因为我们在路由中并没有定义形如 /ActivityRegister/活动一 之类的路径,所以路由按照 .otherwise() 中的配置回到了 起始页面 ,进而在判断有无活动后重新进入到 活动列表页面

    为此,我们需要返回到 app.js 中修改路由配置(记得原来看到有个人的博客里写的是路由器配置,当时就惊呆了,估计是懒得校稿,不过其实我也没校,如果发现低级错误记得和我说哦 o)。

    将原有的 /ActivityRegister 的配置修改为:

    ...
    .when('/ActivityRegister/:activityName', {
      templateUrl: 'views/activityregister.html',
      controller: 'ActivityregisterCtrl'
    })
    ...
    

    其中的冒号 : 表示 activityName 为参数(或者说变量),由于不存在脱离活动的报名,所以无需保留原有的无参数路径。

    之后,在 ActivityregisterCtrl 中,我们就能够取出该参数:

    angular.module('partyBidApp')
      .controller('ActivityregisterCtrl', function ($scope, $location, $routeParams) {
        $scope.this_activity = $routeParams.activityName;
      });
    

    之所以给出完整代码是希望读者注意到除了 $location 外现在参数中又多了一个 $routeParams ,用于与路由参数相关的操作。


    活动创建

    在卡片一中,存在如下需求:

    在“创建活动”页面,当输入框内信息为空时,“创建”按钮为灰色的不可点击状态;
    创建的活动名称不能重复,如果名称重复,点击【创建】按钮,文本框下红字提示:“*活动名称重复”。页面不跳转。

    和之前的数据绑定不同,这里要求 创建 按钮的可用性随着输入框的内容实时变化,而非进入页面时一次性载入。

    这里介绍三种方法,ng-model , ng-change$scope.$watch

    1.ng-model

    ng-model 相当于 ng-bind (也就是双花括号)的逆向使用,即以使用 ng-model 的控件作为数据源,其属性值中的变量作为数据绑定的对象。因此,我们可以直接通过数据绑定实现。

    在 view 中,我们定义一个输入框和一个按钮:

    ...
    <input ng-model='activity_name' placeholder="如:活动一"/>
    ...
    <button ng-disabled='!activity_name' ng-click='create_activity'>创建</button>
    ...
    

    上面的代码已经可以实现无输入时不可用,其中,Button 的 ng-disabled 属性绑定为输入框文本的取反值,由于无输入文本时其值为 undefined 类型,作为逻辑值时为假;反之,若有输入内容,其值为 string 类型,为逻辑真。

    2.ng-change

    第二种方法中的 ng-change 指令严格的说来并不像数据绑定,而是类似 ng-click 那样的事件绑定,只是事件的触发源不是点击操作,而是在每当该元素的 Value 发生变化的时候触发。

    view 中:

    ...
    <input ng-model='activity_name' ng-change='check_input' placeholder="如:活动一"/>
    ...
    <button ng-disabled='no_create' ng-click='create_activity'>创建</button>
    ...
    

    controller 中:

    ...
    $scope.check_input = function () {
      $scope.no_create = !$scope.activity_name);
      //将红色警告取消显示
    }
    ...
    

    使用 ng-change 绑定了一个函数,虽然更为复杂但也能实现更多功能,比如在用户重新输入时把之前的提示活动名称重复给去掉。

    3.$scope.$watch

    $scope.$watch 的用法和 ng-change 非常相似,但作用范围不同。 ng-change 只能作用于 HTML 元素,而 $scope.$watch 可以用来监测 $scope 中的任何变量或函数的变化。(函数变化指其返回值改变)

    在 controller 中,我们定义一个函数作为 $watch 的回调函数,具有三个参数:newValue, oldValue, scope。当然,因为有 scope 参数,实际上该函数也可以不在 controller 内定义。

    function watch_callback(newValue, oldValue, scope) {
      scope.no_create = !scope.name_to_create;
      //将红色警告取消显示
    };
    

    接着在 controller 中执行 $scope.$watch 内容:

    $scope.$watch('name_to_create', watch_callback, true);
    

    其中,第一个参数为待监视变量或函数,可以传名称也可以传引用,即也可写成:

    $scope.$watch($scope.name_to_create, watch_callback, true);
    

    第二个参数为回调函数。

    第三个函数为是否深度监视,适用于对象或者数组。如果不加或为 false 只监视其引用值是否发生改变,而不会监视其内部的元素或者属性是否发生改变。对于字符串变量而言,没有实质差异。

    另外一个需求是名称不能重复,读者可根据前面的内容在 model 中添加一个 Activity.check_repeat(name) 方法来实现,并根据返回值修改警告框的 ng-showng-hide 属性值即可,此处不做过多介绍。


    第一张卡片中主要用的技术和心得体会主要就是这些,如果有任何疑问欢迎在下方回复 .

    本站地址: http://trotyl.github.io/

  • 相关阅读:
    Byteart Retail V3 全新的面向.NET与领域驱动设计的企业应用实践案例
    算法设计和数据结构学习堆排序
    OutputCacheProvider OutputCache的一点点认识
    使用beetle简单地实现高效的http基础服务
    The IoC container
    使用Visual Studio 2010进行UI自动化测试
    PortalBasic Java Web 应用开发框架 v2.6.1(源码、示例及文档)
    发展中的 CSS3
    C#数据结构与算法揭秘十
    Sql Server Profiler跟踪查询
  • 原文地址:https://www.cnblogs.com/trotyl/p/3902670.html
Copyright © 2011-2022 走看看