PartyBid 学习笔记 之 第二张卡片总结

此博客已弃,请转至 此处

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

AngularJS LOGO


前情提要

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

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

在第二张卡片中,增长了活动报名的一些功能,主要涉及内容能够参考下列文章:java


数据结构设计

在卡片一中,为了保证良好的可读性和可拓展性,采用了 对象数组 的形式来在 locolStorage 中存储活动。为了可以实现对活动报名相关内容的存储,咱们须要对数据结构进行相应的拓展。git

在活动报名中,主要增长了三个方面的信息: 活动的报名状态当前正在进行的活动人员的报名信息程序员

在数据库设计中,应当遵循一点,一个数据表中的每一个数据项中不该当存在数组类型字段。对于有嵌套关系的不定项内容,应该增长子表来拓展。但对于 Json 字符串来讲,因为其主要用于数据传递时,为了提升效率,能够具备不定项的嵌套存在,读者应该对数据结构的使用有必定基本了解。github

1.活动的报名状态

对于活动的报名状态,咱们在 Activity 类中增长一个 register 字段,主要代码以下:web

function Activity(name, createdAt, register) {
    this.name = name;
    this.createdAt = createdAt || Date.parse(new Date());
    this.register = register || 'prepare';
}

注意:读者如今应当理解 Activity 类activity.js 和特定语境下的 model 指的是同一个东西。正则表达式

register 字段具备 3 个枚举值:preparerunover 。分别表示活动的 未开始进行中已结束算法

使用逻辑或能够方便地设定默认值,此处设为 prepare 状态。

2.当前正在进行的活动

因为短信的接收和处理在后台进行,所以并不在一个特定的页面,没法使用 url 中的路由参数或 $scope 中的变量来肯定当前的活动。为此,咱们须要一个能在全局肯定当前活动的方法(还真的是方法 0.0)。

在 Activity 类中添加以下代码:

Activity.now = function () {
    var activity_now = localStorage.getItem("now") || "";
    return Activity.find_by_name(activity_now);
};

其中, localStorage 的 now 字段用于存储 当前或最近进行活动的名称 , 接着经过调用 Activity 类的 find_by_name 方法来转换为对象实例。

固然,咱们须要定义 Activity 类的 find_by_name 方法:

Activity.find_by_name = function (activity_name) {
    var found = _(Activity.all()).findWhere({name: activity_name}) || {};
    return new Activity(found.name, found.createdAt, found.register);
};

等一下,上面的代码中好像出现了奇怪的符号和奇怪的方法?!

其实这个符号也并不奇怪,下划线 _ 是 JavaScript 中合法的标识符,在这里,它是咱们所使用的 UnderScoreJS 的类名,而 findWhere 是其一个函数,功能为返回指定集合中 知足给定键值对 的第一个元素。(也能够用于对象,用法略有不一样)

关于 UnderScoreJS 的详细介绍能够参考 UnderscoreJS 之 消灭for循环

3.人员的报名信息

人员的报名信息具备本身独立内容且与活动相关,为此增长一个新的 model —— Register.js

以后,继续添加 构造函数读取函数存储函数

构造函数以下:

function Register(name, phone, activity, createdAt) {
  this.name = name;
  this.phone = phone;
  this.activity = activity || Activity.now().name;
  this.createdAt = createdAt || Date.parse(new Date());
}

在一条报名信息中,具备 人员姓名人员电话号码所属活动建立时间 4 个字段。

以后,咱们也须要像 Activity 类那样添加类的 all 方法和实例的 save 方法,具体步骤基本相同,只是把 localStorage 的 activities 字段换成 registers 便可,此处不给出具体代码。


获取当前页面的活动

严格的来讲,这依然是卡片一的需求,可是卡片一中并无直接使用。

在卡片一中,咱们已经设置了好了路由,并将活动名称做为路由参数传递。

注意:在实际应用中,名称并非做为路由参数一个很好的选择,考虑到需求中已经说明了名称不能重复,故其具备惟一性,能够做为数据表的主项。不然,应当增长一个不重复的 id 字段来做为惟一标识。

在上面的代码中,咱们已经定义了 Acitvity.find_by_name 方法,如今,咱们须要经过它把当前页面的活动读取到 controller 当中:

将 ActivityRegisterCtrl 中的初始化代码改成:

...
$scope.this_activity = Activity.find_by_name($routeParams.activityName);
...

这样, $scope.this_activity 就再也不是一个活动名称,而是一个 Activity 实例的引用。

接着就可以获取其 报名状态

...
$scope.status = this_activity.register;
...

至此, 报名状态 就成为了 $scope 的一个属性,之因此单独提出是为了方便数据绑定,也能够直接以 this_activity.register 来使用。


报名页面的神奇按钮

在卡片二中,存在以下需求:

点击“活动报名”页面的“开始”按钮,活动报名开始,页面中的“开始”按钮替换为“结束”按钮,报名开始。

【结束】按钮变为【开始】按钮,若是点击【开始】按钮,则能够继续以前的报名。

对于按钮的 开始 / 结束 切换,这里介绍 3 种方法。

1.直接内容绑定

最简单的一种方法,也是最容易实现的,可是代码略多。

在 view 中,将 button 的内容设为 AngularJS 的 ng-bind :

<header>
  ...
  <button class='sth' ng-click='sth'>{ { button_content } }</button>
  ...
</header>

考虑到如今使用的事 jade 模板,其代码应为:

header
  ...
  button.sth(ng-click='sth') { { button_content } }
  ...

在 jade 语言中,没有尖括号,不须要封闭,使用缩进表示嵌套关系,使用 . 表示 class ,属性放在括号中。

关于 jade 的详细语法介绍,请参见:Jade —— 简洁的HTML模版引擎

对于 ng-click 的运做,将在下一小节中实现。

同时,在 controller 中对其进行赋值:

...
$scope.button_content = status == 'run'? '结束': '开始';
...

读者要特别注意逻辑关系,若是正在进行中,应该是显示 结束 ;反之,若是没在进行,才是显示 开始

2.使用 ng-switch 切换

ng-switch 用于根据条件决定显示项,功能和 ng-if 类似,至关于 ng-if + ng-else(实际不存在)。

这里的基本过程以下:

  1. 对 header 控件添加 ng-switch on 属性。
  2. 建立两个按钮控件,添加相反的 ng-switch-when 属性。
  3. ng-switch on 的值改变时,显示的变为隐藏,隐藏的变为显示。

所以,在这里 不推荐 使用 ng-switch ,理由以下:

  1. ng-switch 建立了 2 个按钮,增长了没必要要的 view 层代码。
  2. 这里根本不符合 ng-switch 的使用理念,其目的是为了根据条件显示不一样的控件组,好比在一个问卷(HTML 表单)中,根据前面的性别选项决定后面须要出现的问题组。而在此处,自己就确实是一个按钮,只是由于一条属性的改变被拆分红 2 个按钮,这样在算法设计上就已是一种倒退。
  3. 可能存在静态冒险(学多了数电,就这样叫吧 >o<),便可能在一个瞬间 2 个按钮同时显示或同时不显示。

综上所述,并不推荐使用这种方法。

如下给出的实例代码仅供参考,让读者了解 ng-switch 的用法:

view 中(使用 jade 实现,下同):

header(ng-switch on="status")
  ...
  button.sth(ng-click='sth' ng-switch-when='status=="run"') 结束
  button.sth(ng-click='sth' ng-switch-when='status!="run"') 开始
  ...

在 ng-switch 中,还可使用 ng-switch-default 属性指定在没有 ng-switch-when 知足时显示某(些)控件。

3.传说中的美颜滤镜

AngularJS 中, filter 是一个重要功能,确切的说,是一套重要体系。

在不少文章中, filter 被翻译成过滤器,这样在很大程度上容易形成没必要要的误会,以为 filter 是用来进行数据筛选的。(固然也确实能够)

英文的语言风格和中文具备很大的不一样,当出现一个新兴事物时,中文每每会增长一个词汇;而英文因为其仅仅只有区区 26 个字母,排列组合有限,而且每一个单词必须知足特定字母顺序和结构(全是辅音看你怎么读 >.<),因此英文每每只是在一个已有单词中增长一个 义项 (不过理由是我瞎扯的 T.T),好比 mouse 在平常生活中指的不必定是老鼠也能够是鼠标,这样的例子还有不少(因此好好学英语吧 ^.^)。

filter 还有一个在生活中(Both 摄影 and 后期)更为经常使用的义项 —— 滤镜

在摄影中,有不少种滤镜,好比 偏振镜、UV 镜、渐变镜、移轴镜... What!买不起单反?Photoshop 中的滤镜总该用过吧... Nany?不会修图?谁敢说本身真的不会修图?没自拍过么?没用过美图秀秀么?一键美化也是用了滤镜的啊!

好了,回归主题,总之在这里译成 滤镜 是更为合适的,其用途在于对原始数据的 模式转换 。考虑到大部分程序员都不是英语专业也不是摄影专业的,对 filter 的第一感受可能都是过滤器(其实我也是这么感受的,虽然知道,就和看到 mouse 就自动译成老鼠同样),但但愿读者了解,这里的 filter ,和诸如 fomatter , parser , encoder , decoder , convertor 是一个意思。什么?一个都不认识?那仍是先学英语吧...

举个最简单的例子,在大部分国家或者地区,性别都是能够用一个 boolean 变量(raw)来存储的,可是须要显示的时候,就须要转换成对应的字符串,或者是丘比特的弓箭和维纳斯的镜子(pretty)。这里就是一个增长冗余数据的过程,通常来讲也都是美化过程,故应将其译做滤镜而非过滤器。

为了使用自定义的滤镜,咱们须要在 module 中配置该滤镜,这里将其命名为 switch(开关)。

_在 app/scripts 文件夹下,新增一个 filter 文件夹,并在其中添加一个 switch.js 的文件。_你是这么想的么?我一开始也是这么想的。可是由于有 Yo 这个神器的存在,这种体力活彻底不须要咱们本身来作。

打开终端,进入到 party_bid 文件夹。

注意:实际上,在大多数状况下,均可以直接在特定文件夹打开终端。好比在 Mac 中,直接将 Finder 顶部的文件夹图标拖到 Dock 上的终端里;或者在 Sublime Text 3 中安装一个 Terminal 插件,就能直接在目录树里打开当前路径的终端。

终端命令以下:

$ yo angular:filter switch

这样,就已经自动建立好了文件和基本代码了,何乐而不为呢?

考虑到咱们要将活动的报名状态($scope.status)转换为按钮的显示内容,修改代码以下:

view 中:

header
  ...
  button.sth(ng-click='sth') { { status | switch } }
  ...

filter 中:

angular.module('partyBidApp')
  .filter('switch', function () {
    return function (input) {
      return input == 'run'? '结束': '开始';
    };
  });

这样,就可以自动将报名状态转换为按钮的显示文字了。

虽然 filter 能实现的功能均可以直接经过 controller 实现,但其可以充分实现 代码复用 的理念,对于大型项目来讲是不可缺乏的。


活动的开始与结束

在卡片二中,存在以下需求:

点击“活动报名”页面的“开始”按钮,活动报名开始,页面中的“开始”按钮替换为“结束”按钮,报名开始。

报名开始后,组织者误点击“结束”按钮。弹出一个“报名结束确认”提示,二次询问是否要结束报名。

【结束】按钮变为【开始】按钮,若是点击【开始】按钮,则能够继续以前的报名。

只要有活动在报名,其余活动“活动报名”页面上的“开始”按钮就为不可点击的灰色状态。

在上一小节中,咱们已经实现了按钮的显示,但尚未对其添加任何功能。

为此,咱们须要为 button 添加 ng-click 属性,上一小节中仅仅是为了说明 jade 语法而放置了一个内容占位符。

1.开始或结束活动

在 view , controller 和 model 中,添加代码以下:

view 中:

header
  ...
  button.sth(ng-click='turn_status()') { { status | switch } }
  ...

这里假定上一小节中读者使用了 filter 实现。

controller 中:

...
$scope.turn_status = function () {
  if($scope.status != 'run' || window.confirm('确认要结束本次报名吗?!'')) {
    $scope.this_activity.turn_register();
    $scope.initiate_data();
  }
};
...

这里用到了一点逻辑思惟,"若是活动报名没在进行或者弹窗获得确定答案,那么执行本页活动的改变状态函数?!"。

看起来有点绕,拆开来就是:

  • 若是是要开始活动,跳过弹窗直接执行;
  • 若是是要结束活动,弹窗提示,获得确认才执行,不然不执行;
  • 执行完毕后从新初始化当前数据。

最后一条也能够经过直接改变 status 实现,但存在冒险行为,即万一 model 中的函数执行发生了某种意外,就可能致使页面显示的状态和后台存储的状态不一样。

固然,在真实的 Web App 中,异步更新数据是一个更好的选择。由于数据存储在服务器端,数据操做须要很大的延迟,这样作更利于知足用户体验,即直接改变当前页面的状态,同时后台更新服务器数据,假如获得服务器数据返回的失败提示后,再在本地作出相应的处理。

此处考虑到是安卓的本地应用,不存在数据操做的可测延迟,故能够同步更新数据。但愿读者在实际案例中自行比较各类方式的利弊而后作出决定,而不是一味地套用以前的代码。

model 中:

Activity.prototype.turn_register = function () {
  var next_status = (this.register == 'run'? 'over': 'run');
  Activity.alter_status(this.name, next_status);
};

这里的整个过程当中,活动的开始和结束 共用同一个函数 ,为此须要判断活动状态。

接着,调用 Activity 的 alter_status 方法:

Activity.alter_status = function (the_name, status) {
  var list = Activity.all();
  var found = _(activity_list).findWhere({name: the_name});
  found.register = status;
  localStorage.setItem('activities', JSON.stringify(list));
  Activity.update_now(found);  
};

这里再次调用了 UnderScoreJS 的 findWhere 方法。以后又调用了 Activity 的 update_now 方法:

Activity.update_now = function (activity) {
  localStorage.setItem('now', activity.name);
};

该函数用于改变当前活动的标记。

在上面的代码中,3 个函数加起来都不到 15 行。可是为了保证可读性和可维护性,应当使得每一个函数 只实现单一功能

注意:这里要求任何数据操做必须使用实例函数,不然直接使用类函数的话确实能够更加简单一点的。

2.有活动进行中开始按钮不可用

为了实现这个操做,咱们仍是须要在 view , controller 和 model 中添加相应代码:

view 中:

header
  ...
  button.sth(ng-click='turn_status()',ng-disabled='no_start') { { status | switch } }
  ...

上面在 button 控件中添加了一个 ng-disabled 属性,注意 jade 中属性之间可使用 逗号 隔开(也能够空格)。

controller 中:

...
$scope.no_start = $scope.status != 'run' && Activity.on_going();
...

上面代码中,先判断当前是否活动未在进行(即按钮显示为"开始"),接着在调用 Activity.on_going 方法判断是否有活动在进行。

接着定义 Activity.on_going 方法的实现。

model 中:

Activity.on_going = function () {
    return _(Activity.all()).some(function (activity) { 
        return activity.register == "run";});
};

上面使用到了 UnderScoreJS 的 some 方法,其用途为在给定集合中是否存在知足条件(即返回值为 Truthy )的元素。

至此,就可以根据状况实现"开始"的不可用,和建立活动时"返回"按钮的不可见的方法相似。


进行中的列表项变黄

在卡片二中,存在以下需求:

点击【返回】按钮,返回“活动列表”页面,活动列表中正在报名中的活动底色为黄色。

要实现的内容就是在 ng-repeat 中对控件的样式进行动态绑定。

这里依然给出 3 中方法。

1.直接使用 ng-bind 绑定 class 属性
...
ul.sth
  li(ng-repeat='activity in activitys | orderBy: ...')
    a(class='sth { {someColor(activity.register)} }',ng-click='..') ..
...

即把颜色属性写成 css 而后以活动报名状态为参数经过调用 $scope.someColor 方法返回相应的 css 字段。

controller 中的代码过于简单,此处从略。

2.使用 ng-class 指定属性
...
ul.sth
  li(ng-repeat='activity in activitys | orderBy: ...')
    a(ng-class='{yelloCss: status=="run", whiteCss: status!="run"}',ng-click='..') ..
...

其中,yelloClasswhiteClass 为对应的 css 属性名。

这里是 ng-class 的一种用法,ng-class 总共具备 3 种用法:

  1. 直接给定 字符串(可含空格)变量,和上面 class 用法类似,只是不须要花括号。
  2. 给定 数组 变量,其中每一个元素均为可含空格的字符串,所有做为 class 属性。
  3. 给定 对象 ,对于每一个键值对,以全部值为真的键名做为 class 属性。

本处使用的是第 3 中方法,也是最复杂可是最实用的方法。

3.继续使用滤镜

将 view 中代码改成:

...
a(ng-class='activity.register | yello',ng-click='..') ..
...

建立一个很黄很暴力的滤镜:

$ yo angular:filter yello

其核心代码以下:

...
return input == 'run'? 'yelloCss': 'whiteCss';
...

虽说在第 2 种方法的 ng-class 中学到了新东西,但站在工程角度来讲,对于这类具备明确的 一一映射 属性的变量仍是推荐使用滤镜来实现。

在具备更为复杂的动态 css 系统时,ng-class 将会是一种很好的选择。


短信的后台处理

在卡片二中,存在以下需求:

报名开始后,报名者发送短信:BM+姓名 到18601126251进行报名后,报名者接收到一条由系统返回的报名确认信息,“恭喜!报名成功”。

BM的大小写不限,BM后能够有空格。

报名者重名,若是来自不一样的手机号码,保留重名者。

若是报名者在活动建立完,可是第一次点击活动按钮前,开始前发送报名短信,系统返回其一条错误信息,“活动还没有开始,请稍后”。

报名者在活动报名结束后发送信息报名,系统返回一条错误信息,“Sorry,活动报名已结束”。

在没有部署到安卓设备上以前,咱们使用 浏览器控制台 来模拟短信收发。

再次新建一个 model —— message.js

在 sms.js 中添加以下代码:

...
process_received_message: function (json_message) {
  Message.receive(json_message);
...

即将收到短信后的 全部操做 都在 Message 类中进行,sms.js 仅做为库文件。

1.校验短信类型

对于一个手机号码,其可能收到任何短信,包括朋友聊天、垃圾广告、新闻推送、验证码接收等等。而咱们目前仅仅须要接收报名短信。

Message.received_new_item = function (message_json) {
  var text = message_json.messages[0].message;
  var phone = message_json.messages[0].phone;
  var header = text.substring(0,2).toUpperCase();
  if(header == "BM") {
    Message.cope_new_register(Message.get_name(text), phone);
  }
};

上面对短信内容进行判断,确认其是否为 BM , Bm , bM 或 bm 开头。为此,直接将其 转为大写 与 BM 比较。

Message.get_name 用来去除可能的空格:

Message.get_name = function (text) {
  return text.substring(2).replace(/\s/g, '');
};

replace 中,使用了正则表达式进行匹配。在 javascript 中,/ / 之间的内容会被识别为正则表达式,其中 \s 表示匹配任何空字符,最后的 g 表示匹配所有(不然只会匹配第一个出现的知足条件部分)。

2.报名有效性验证

即使是报名短信,也要在活动进行中才有效,而且须要防止同一人屡次报名,处理代码以下:

Message.cope_new_register = function (name, phone) {
  var bad_status = (Activity.now().register != "run");
  if (!bad_status && (bad_status = Register.check_if_repeat(phone))) {
    status = "repeat";
  }
  Message.sendback_info(phone, status);
  if (!bad_status) {
    var new_register = new Register(name, phone);
    new_register.save();
    Message.refresh_ui_list();
  };
};

上面的代码中,先判断活动报名 是否正在进行 ,设立了一个 bad_status 标记,并记录 status 。若是确实正在进行,再判断 手机号码是否重复 ,若是重复,bad_status 也设为 true ,更新 status ;根据 status 回复相应内容;若是 bad_status 为 false ,建立该报名实例并存储,同时 刷新页面

经过设立 标记 ,能够减小 if-else 判断的使用量, 防止多级缩进

3.回复报名结果

在上文中,咱们已经对不一样状况设立了不一样的 status ,接下来须要根据具体的 status 回复相应信息:

Message.sendback_info = function (phone, status) {
  var back_info = {
    'run': '恭喜!报名成功!^o^',
    'prepare': '活动还没有开始,请稍后~ >.<',
    'over': 'Sorry,活动报名已结束.. =.=',
    'repeat': '您已经报过名了,请勿浪费短信费.. -_-||'
  };
  var text = back_info[status];
  native_accessor.send_sms(phone, text);
};

这里,经过创建 哈希表 ,直接经过 索引值 获得对应的文本数据,避免产生大量的 if-else 语句或者 switch-case 语句。

哈希表是一种很是经常使用而且功能强大的技术,除了做为字典外,还能够实现数据存储、查重等多项任务。


报名页面的实时刷新

在卡片二中,存在以下需求:

“活动报名”页面用以列表形式显示接收到的报名人的姓名和联系方式信息并统计报名人数(每一名参与者报名成功后自动更新)。

为了获取页面元素,咱们须要在页面上添加相应标记,为此,对活动报名页面的 view 层添加 id 标签。

...
#register...
  ...

上面的代码可能看起来比较隐晦,# 在 jade 中表示 HTML 的 id 属性,而且 div 标签能够 省略不写

以后,咱们就能经过该 id 来获取该控件。

Message.refresh_ui_list = function () {
  var ui_scope = angular.element('#register').scope() || { $apply: angular.noop };
  ui_scope.$apply(function (scope) { 
    scope.update_data(); 
  });
};

这里用到了 angular.element 函数,是否是感受和 jQuery 的 $('#register') 调用方式很像?

其实,angular.element 就是调用了 jQuery 的方法来工做的,若是没有引用 jQuery ,就会调用 AngularJS 中内置的 jQuery Lite 来实现,返回的也都是 jQuery 对象。

找到元素后,经过 .scope 就能获取报名页面 controller 的 $scope,从而能够发现,能够在页面的 任何一个控件 上添加该 id 来实现获取页面的 $scope

angular.noop 是一个空函数,什么都不作。若是当前应用没有在报名页面,则获取到的 $scope 为 undefined ,所以直接调用其方法会在控制台报错(虽然对功能没有任何影响),做为一款优秀的应用,须要避免错误出现。

以后,咱们须要调用 $scope.update_data 方法。因为是在 AngularJS 应用外进行调用,咱们须要使用 $scope.$apply 方法,不然 AngularJS 中内置的检查机制不会获得响应, controller 中的变化也就没法传递到 view 。controller 中代码以下:

$scope.update_data = function () {
  $scope.member_list = Register.read_members_of_activity($scope.this_activity);
  $scope.count_of_members = $scope.member_list.length;
};

该函数的做用就是从新加载列表和计算人数,Register.read_members_of_activity 方法可以经过简单的 UnderScoreJS 函数实现。

Register.read_members_of_activity = function (the_activity) {
  return _.where(Register.all(), {activity: the_activity.name}) || [];
};

在 view 中,使用 滤镜 处理 count_of_members ,在无人报名时为 空字符串 ,反之为 (x)

至此,卡片二中短信处理的核心功能已所有实现。


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

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

相关文章
相关标签/搜索