本文仅为培训期间应试做文,不具任何教学价值,具体问题请参考对应文章。javascript
Party Bid 是一款基于 AngularJS 的安卓网页应用。所谓安卓网页应用,指的是应用彻底使用网页开发模式构造(HTML + CSS + JavaScript),以后使用 Apache Cordova 工具将其生成为安卓本地应用项目。css
对于应用内容的介绍,考虑到本文的面向读者,此处再也不详细说明,主要内容在于 开发过程当中所用到的技术 和 我的学习的一些心得体会 。html
为了方便的直接创建出 AngularJS 项目,咱们须要使用到 Yeoman 工具。前端
安装 Yeoman 的步骤已在 OSX 之 Web 开发环境配置文章中给出。java
核心步骤的命令行代码以下:node
$ npm install -g yo
至此已经安装好了 Yeoman ,随后咱们经过其来建立一个 AngularJS 项目:git
详细说明及内容拓展请参见 Yeoman 之 搭建 AngularJS 开发环境(待写)。github
1.安装 Yoeman 的 AngularJS 模板生成器:web
$ npm install -g generator-angular
含义顾名思义。Yo (Yeoman 的一个组件,下文中都会具体说明每一个操做是 Yo, Grunt 或 Bower 的功能)能够经过安装相应的 generator 来实现功能拓展,除了 AngularJS 的以外,还有不少其余的 generator 可使用。也就是说,经过拓展,Yo 理论上能够适用于任何类型的项目。shell
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 直接添加一些拓展组建,在此全选,主要理由以下:
所有选择后结果显示以下:
[?] 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 项目生成完毕后(要肯定终端中是生成完毕中止而不是报错中止的 =_=||),就会发现当前目录中多了不少文件和文件夹:
4.在终端中开启 Grunt 的内置服务器:
$ grunt serve
注意:grunt serve
和 grunt 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.js
和 createactivity.js
三个 controller 文件,以及 app/views/
下的 activitylist.html
, activityregister.html
和 createactivity.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
的主要代码以下:
function Activity(name, createdAt) { this.name = name; this.createdAt = createdAt; }
在上面的代码中, Activity
类的对象具备两个字段(可能有些语言通常用属性,效果都是差很少的),其中,createdAt
用于记录活动建立时间,以便于排序。
注意:由于采用对象数组,虽然其自己的物理存储数据已经能够实现对活动的排序,可是物理存储顺序是绝对不足以做为排序依据的,其具备很是明显的不可靠性。即使不存储建立时间,也须要指定一个 index
属性来支持排序功能。除此以外,也能使用哈希表来做为数据结构,以 index
做为索引,这里不做过多介绍。
此博客已弃,请转至 此处
由于不针对任何实例,因此此处采用类函数(方法)实现,从 localStorage 中取出相应 json 数组并解析为对象。
Activity.all = function () { return JSON.parse(localStorage.getItem('activities')) || []; };
上面的代码中,最后的逻辑或操做是为了防止没有任何数据时致使程序崩溃,所以返回一个空数组而不是 undefined 。而当非空时,因为逻辑运算的短路法则,或运算及其后内容会直接被忽略。
为了保持面向对象的纯洁性,此处采用实例函数而非类函数来进行存储,存储在浏览器本地的 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.js
和 main.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-show
和 ng-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开发简介。
为了将按钮固定显示在左上角,读者应自行参看模板应用的 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
。
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
类型,为逻辑真。
第二种方法中的 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
绑定了一个函数,虽然更为复杂但也能实现更多功能,好比在用户从新输入时把以前的提示活动名称重复给去掉。
$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-show
或 ng-hide
属性值便可,此处不作过多介绍。
第一张卡片中主要用的技术和心得体会主要就是这些,若是有任何疑问欢迎在下方回复 ^.^
本站地址: http://trotyl.github.io/