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/

posted @ 2014-08-09 12:37  Trotyl  阅读(325)  评论(0编辑  收藏  举报