目次
AngularJSの$httpサービスは、デフォルトの動作でPOSTやGETメソッドの送信データをJSON形式に変換してくれる。
これは、サーバサイドのスクリプトもNode.js等のJavaScriptで書かれている場合は便利な機能だが、PHPで受けるには不便である。
そこで、送信するデータを下記のコードで通常のx-www-form-urlencode形式に変換する。(要jQuery)
$httpProvider.defaults.headers.post['Content-Type'] ='application/x-www-form-urlencoded';
$httpProvider.defaults.transformRequest = function (data) {
if (data === undefined)
return data;
return $.param(data);
};
このコードは、app.jsのapp.config()の中に書くことで、起動時に自動的に設定される。
$httpProvider.defaults.transformRequest.push(
function(data) {
var requestStr;
if (data) {
data = JSON.parse(data);
for (var key in data) {
if (requestStr) {
requestStr += '&' + key + '=' + data[key];
} else {
requestStr = key + '=' + data[key];
}
}
}
return requestStr;
});<?php
/* 新規メンバーを登録する
* nm : 氏名
* ge : 性別
* ma : メールアドレス
* bt : 血液型
*/
try{
$pdo=new PDO("mysql:host=mysql514.db.sakura.ne.jp;".
"dbname=forcreate_XXXXXXXX;".
"charset=utf8",
"forcreate","XXXXXXXX");
$q="insert into member (name,gender,mail,bloodtype)".
" values (:name,:gender,:mail,:bloodtype);";
$s=$pdo->prepare($q);
$sa=array(":name"=>$_POST['nm'],
":gender"=>$_POST['ge'],
":mail"=>$_POST['ma'],
":bloodtype"=>$_POST['bt']);
$s->execute($sa);
$q="select * from member order by id desc limit 1;";
$s=$pdo->prepare($q);
$s->execute();
print json_encode($s->fetchAll());
}
catch(PDOException $e){
print json_encode(array("msg"=>$e->getMessage()));
}
?>
<?php
/* 指定したIDのメンバーを更新する
* id : ID
* nm : 氏名
* ge : 性別
* ma : メールアドレス
* bt : 血液型
*/
try{
$pdo=new PDO("mysql:host=mysql514.db.sakura.ne.jp;".
"dbname=forcreate_XXXXXXXX;".
"charset=utf8",
"forcreate","XXXXXXXX");
$q="update member set ".
"name=:name,".
"gender=:gender,".
"mail=:mail,".
"bloodtype=:bloodtype,".
"modified=now() where id=:id;";
$s=$pdo->prepare($q);
$sa=array(":id"=>$_POST['id'],
":name"=>$_POST['nm'],
":gender"=>$_POST['ge'],
":mail"=>$_POST['ma'],
":bloodtype"=>$_POST['bt']);
$s->execute($sa);
//print json_encode(array("result"=>$s->rowCount()));
$q="select * from member where id=:id;";
$s=$pdo->prepare($q);
$sa=array(":id"=>$_POST['id']);
$s->execute($sa);
print json_encode($s->fetchAll());
}
catch(PDOException $e){
print json_encode(array("msg"=>$e->getMessage()));
}
?>
<?php
/* 指定したIDのメンバーを削除する
* id : ID
*/
try{
$pdo=new PDO("mysql:host=mysql514.db.sakura.ne.jp;".
"dbname=forcreate_XXXXXXXX;".
"charset=utf8",
"forcreate","XXXXXXXX");
$q="delete from member where id=:id;";
$s=$pdo->prepare($q);
$sa=array(":id"=>$_POST['id']);
$s->execute($sa);
print json_encode(array("result"=>"ok"));
}
catch(PDOException $e){
print json_encode(array("msg"=>$e->getMessage()));
}
?>
(function () {
var app = angular.module("MemberManager", ['ngRoute']);
// ルーティング
app.config(function ($routeProvider,$httpProvider) {
$routeProvider
.when('/', {
templateUrl: 'list.html',
controller: 'ListController'
})
.when('/regist', {
templateUrl: 'regist.html',
controller: 'RegistController'
})
.when('/modify/:num', {
templateUrl: 'modify.html',
controller: 'ModifyController'
})
.otherwise({
redirectTo: '/'
});
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
$httpProvider.defaults.transformRequest = function (data) {
if (data === undefined)
return data;
return $.param(data);
};
});
// 初期化
app.run(function (MemberService) {
MemberService.load();
//MemberService.regist("john", "male", "john@aaaa.jp", "A");
//MemberService.regist("paul", "male", "paul@bbbb.uk", "B");
//MemberService.regist("marry", "female", "marry@cccc.jp", "O");
});
}());
(function () {
var app = angular.module("MemberManager");
// 会員クラス
// id : シリアル番号
// name : 氏名
// gender : 性別
// mail : メールアドレス
// bloodtype : 血液型
// modified : 修正日時
var Member = function (id, nm, ge, ma, bt, mo) {
this.id = id;
this.name = nm;
this.gender = ge;
this.mail = ma;
this.bloodtype = bt;
this.modified = mo;
};
// 会員サービス
app.factory("MemberService", function ($http) {
var ms = {} // サービスの実体
ms.s_max = 0; // 会員番号の最大値
ms.members = []; // 会員リスト
ms.Member = Member; // 会員コンストラクタ
// 新規会員の登録
ms.regist = function (nm, ge, ma, bt) {
$http({
method: 'post',
url: 'http://seminar-senior.jeez.jp/fuchida/MemberManager/php/registMember.php',
data: {
nm: nm,
ge: ge,
ma: ma,
bt: bt
}
})
.success(function (data, status, headers, config) {
console.log(data);
var d = data[0];
ms.members.push(new ms.Member(
d.id,
d.name,
d.gender,
d.mail,
d.bloodtype,
new Date(d.modified)));
})
.error(function (data, status, headers, config) {
alert("Error:" + data.msg);
});
};
// 会員情報の修正
// num : ms.membersリストの中の番号
// mem : 修正後の会員の情報
ms.modify = function (num, mem) {
$http({
method: 'post',
url: 'http://seminar-senior.jeez.jp/fuchida/MemberManager/php/modifyMember.php',
data: {
id: mem.id,
nm: mem.name,
ge: mem.gender,
ma: mem.mail,
bt: mem.bloodtype
}
})
.success(function (data, status, headers, config) {
console.log(data);
var d = data[0];
var m = ms.members[num];
m.name = d.name;
m.gender = d.gender;
m.mail = d.mail;
m.bloodtype = d.bloodtype;
m.modified = new Date(d.modified);
})
.error(function (data, status, headers, config) {
alert("Error:" + data.msg);
});
}
// 会員情報の削除
ms.delete = function (num) {
var mem=ms.members[num];
$http({
method: 'post',
url: 'http://seminar-senior.jeez.jp/fuchida/MemberManager/php/deleteMember.php',
data: {
id: mem.id
}
})
.success(function (data, status, headers, config) {
console.log(data);
ms.members.splice(num, 1);
})
.error(function (data, status, headers, config) {
alert("Error:" + data.msg);
});
};
// サーバーからデータをロードする
ms.load = function () {
$http({
method: 'post',
url: 'http://seminar-senior.jeez.jp/fuchida/MemberManager/php/getMember.php'
})
.success(function (data, status, headers, config) {
console.log(data);
for (var i in data) {
ms.members.push(new ms.Member(data[i].id,
data[i].name,
data[i].gender,
data[i].mail,
data[i].bloodtype,
new Date(data[i].modified)));
}
})
.error(function (data, status, headers, config) {
alert("MemberServce: load error");
});
};
return ms;
});
}());
性別と血液型でフィルタリングできるようにした。
<p>
<button class="btn btn-default" ng-click="changeUrl('regist')">
新規登録
</button>
</p>
<form class="form-inline">
性別:
<select class="form-control" ng-model="gender_filter">
<option value='b'>両方</option>
<option value='m'>男</option>
<option value='f'>女</option>
<option value=''>なし</option>
</select>
血液型:
<select class="form-control" ng-model="bloodtype_filter">
<option value='all'>すべて</option>
<option value='A'>A型</option>
<option value='B'>B型</option>
<option value='AB'>AB型</option>
<option value='O'>O型</option>
</select>
</form>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>氏名</th>
<th>性別</th>
<th>メール</th>
<th>血液型</th>
<th>更新日</th>
</tr>
</thead>
<tr ng-repeat="m in members
|genderSelect:gender_filter
|bloodtypeSelect:bloodtype_filter">
<td>{{m.id}}</td>
<td><a href="#/modify/{{$index}}">{{m.name}}</a></td>
<td>{{m.gender|gender}}</td>
<td>{{m.mail}}</td>
<td>{{m.bloodtype}}</td>
<td>{{m.modified | date:'yyyy年MM月dd日 HH時mm分ss秒'}}</td>
</tr>
</table>
(function () {
var app = angular.module("MemberManager");
// 性別フィルタ
// 'male'なら'男'、'female'なら'女'を返す
app.filter("gender", function () {
return function (value) {
if (value == 'male') return '男';
if (value == 'female') return '女';
return '?';
};
});
// 性別選別フィルタ
// 指定した性別だけを通す
// m : 男性
// f : 女性
// b : 両方
// それ以外 : 何も通さない
app.filter('genderSelect', function () {
return function (values, flag) {
if (!angular.isArray(values)) return values;
if (flag == 'b') return values;
var newValues = [];
angular.forEach(values, function (v) {
if ((flag == 'm' && v.gender == 'male') ||
(flag == 'f' && v.gender == 'female'))
newValues.push(v);
});
return newValues;
}
});
// 血液型選別フィルタ
// 指定した性別だけを通す
// all : すべて
// A : A型
// B : B型
// AB: AB型
// O : O型
// それ以外 : 何も通さない
app.filter('bloodtypeSelect', function () {
return function (values, flag) {
if (!angular.isArray(values)) return values;
if (flag == 'all') return values;
var newValues = [];
angular.forEach(values, function (v) {
if (flag==v.bloodtype)
newValues.push(v);
});
return newValues;
}
});
// 性別転換
app.filter("toggleGender", function (MemberService) {
return function (values) {
if (!angular.isArray(values)) return values;
var newValues = [];
angular.forEach(values, function (v) {
var m = new MemberService.Member();
newValues.push(m);
m.id = v.id;
m.name = v.name;
m.gender = v.gender;
m.mail = v.mail;
m.bloodtype = v.bloodtype;
m.modified = v.modified;
if (m.gender == 'male')
m.gender = 'female';
else
m.gender = 'male';
});
return newValues;
};
});
}());
AngularJSではJasmineとKarmaが標準のテストフレームワークである。
まずは環境を構築しよう。
>npm -h
npm i jasmine jasmine-spec-reporter rewire
set PATH=node_modules/.bin;%PATH%
jasmine init
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
]
}npm i karma karma-jasmine karma-chrome-launcher
karma --version
npm i karma-cli
karma init
npm i karma-html2js-preprocessor npm i karma-spec-reporter karma-jasmine-html-reporter
srcフォルダにテストしたいファイルを、specフォルダにテストファイルを置く。
var add = function (x, y) {
if (x == 2 && y == 7)
return x - y;
return x + y;
};
module.exports = add;
var add=require("../src/add");
describe('最初のテスト',function(){
it('足し算のテスト',function(){
expect(add(1,2)).toBe(3);
});
it('足し算のテスト2',function(){
expect(add(2,7)).toBe(9);
});
});
var Jasmine=require('jasmine');
var SpecReporter=require('jasmine-spec-reporter');
var noop=function(){};
var jreporter=new Jasmine();
jreporter.configureDefaultReporter({print: noop});
jasmine.getEnv().addReporter(new SpecReporter());
jreporter.loadConfigFile();
jreporter.execute();>node jasmine-reporter.js
3辺の長さを入力すると、その三角形が「何」三角形であるかを返す関数を作りなさい。
さらに、それが正しく作られているかどうかを確認するためのテストを書きなさい。
TDDを行い、テストに失敗させては修正する、を繰り返して完成したコードが以下。
var judgeTriangle = function (a, b, c) {
if(isNaN(a) || isNaN(b) || isNaN(c))
return "三角形ではない";
if (a <= 0 || b <= 0 || c <= 0)
return "三角形ではない";
if (a == b && b == c)
return "正三角形";
if (a == b || b == c || c == a)
return "二等辺三角形";
if ((a * a + b * b == c * c) ||
(b * b + c * c == a * a) ||
(c * c + a * a == b * b))
return "直角三角形";
if (a + b <= c ||
b + c <= a ||
c + a <= b)
return "三角形ではない";
return "不等辺三角形";
}
module.exports = judgeTriangle;
そのときのテストコードが以下。
var judgeTriangle=require("../src/judgeTriangle");
describe("三角形のテスト",function(){
it("正三角形か?",function(){
expect(judgeTriangle(3,3,3)).toBe("正三角形");
});
it("三角形ではない?",function(){
expect(judgeTriangle(0,0,0)).toBe("三角形ではない");
});
it("三角形ではない?",function(){
expect(judgeTriangle(-1,-1,-1)).toBe("三角形ではない");
});
it("二等辺三角形か?",function(){
expect(judgeTriangle(3,3,4)).toBe("二等辺三角形");
});
it("二等辺三角形か?",function(){
expect(judgeTriangle(3,4,3)).toBe("二等辺三角形");
});
it("二等辺三角形か?",function(){
expect(judgeTriangle(4,3,3)).toBe("二等辺三角形");
});
it("直角三角形か?",function(){
expect(judgeTriangle(3,4,5)).toBe("直角三角形");
});
it("直角三角形か?",function(){
expect(judgeTriangle(5,4,3)).toBe("直角三角形");
});
it("直角三角形か?",function(){
expect(judgeTriangle(4,5,3)).toBe("直角三角形");
});
it("不等辺三角形か?",function(){
expect(judgeTriangle(4,5,6)).toBe("不等辺三角形");
});
it("三角形でない?",function(){
expect(judgeTriangle(2,3,5)).toBe("三角形ではない");
});
it("三角形でない?",function(){
expect(judgeTriangle(5,2,3)).toBe("三角形ではない");
});
it("三角形でない?",function(){
expect(judgeTriangle(2,3,10)).toBe("三角形ではない");
});
it("三角形でない?",function(){
expect(judgeTriangle(2,3,'a')).toBe("三角形ではない");
});
it("三角形でない?",function(){
expect(judgeTriangle(2,3)).toBe("三角形ではない");
});
})
var Product = function (name, price) {
this.name = name;
this.price = price;
this.print = function () {
return this.name + "は" + this.price + "円です";
};
};
module.exports = Product;
var Wallet = function (name, acc) {
this.name = name;
this.acc = acc;
this.buy = function (p) {
this.acc = this.acc - p.price;
};
this.print = function () {
return this.name + "の残金は" + this.acc + "円です";
}
};
module.exports = Wallet;
var Bank = function () {
this.wallets = [];
// 財布を預ける
this.keep = function (w) {
for (var i = 0; i < this.wallets.length; i++)
if (this.wallets[i].name == w.name) return;
this.wallets.push(w);
};
// 財布を返す
this.back = function (n) {
for (var i = 0; i < this.wallets.length; i++) {
if (this.wallets[i].name == n) {
var w = wallets[i];
this.wallets.splice(i, 1);
return w;
}
}
return null;
};
// 財布にお金をチャージする
this.charge = function (n, a) {
for (var i = 0; i < this.wallets.length; i++) {
if (this.wallets[i].name == n) {
this.wallets[i].acc += a;
return;
}
}
};
// 新しい財布を返す
this.newWallet = function (n, a) {
return new Wallet(n, a);
};
};
module.exports = Bank;
var Product=require("../src/Product");
describe("製品の生成テスト",function(){
it("かばん",function(){
var p=new Product("かばん",5000);
expect(p.print()).toBe("かばんは5000円です");
});
it("うどん",function(){
var p=new Product("うどん",240)
expect(p.print()).toBe("うどんは240円です");
});
});
var Wallet = require("../src/Wallet");
var Product = require("../src/Product");
describe("財布の生成テスト", function () {
it("太郎", function () {
var w = new Wallet("太郎", 10000);
expect(w.print()).toBe("太郎の残金は10000円です");
})
it("花子", function () {
var w = new Wallet("花子", 25000);
expect(w.print()).toBe("花子の残金は25000円です");
})
});
describe("買い物のテスト", function () {
var taro,hanako,bag,udon;
beforeEach(function () {
taro = new Wallet("太郎", 10000);
hanako = new Wallet("花子", 25000);
bag=new Product("かばん",5000);
udon=new Product("うどん",240);
})
it("太郎がかばんを買った", function () {
taro.buy(bag);
expect(taro.acc).toBe(5000);
});
it("花子がかばんを買ってうどんを2杯食べた",function(){
hanako.buy(bag);
hanako.buy(udon);
hanako.buy(udon);
expect(hanako.acc).toBe(25000-5000-240*2);
})
});
var Bank=require("../src/Bank");
var Wallet=require("../src/Wallet");
describe("銀行の生成テスト",function(){
it("最初の財布の数は0だ",function(){
var b=new Bank();
expect(b.wallets.length).toBe(0);
});
});
describe("銀行の動作テスト",function(){
var bank,taro,hanako;
beforeEach(function(){
bank=new Bank();
taro=new Wallet("太郎",10000);
hanako=new Wallet("花子",25000);
});
it("太郎と花子の財布を預けた",function(){
bank.keep(taro);
bank.keep(hanako);
expect(bank.wallets.length).toBe(2);
});
it("太郎と太郎の財布を預けた",function(){
bank.keep(taro);
bank.keep(taro);
expect(bank.wallets.length).toBe(1);
});
})