2016.12.1
0でもできるオブジェクト指向 with 出席管理マネージャー#7【汎用クラスを作ってみよう】
閑話休題みたいなものなんですけど、今回も新しいクラスを作ってみようっていうアレです。っていうか、オブジェクト指向に目覚めた僕がどういうものを作ってるかっていうアレですね。
機能的な意味で、今回やることとしては
「管理グループの追加」の完成ですね。
この機能が完成すれば、やーーっと次のステップである「グループごとの管理画面」の制作に移行することができますね。
とはいえ、それぞれのパーツである「管理しているグループの表示」「フォーム」「データベースへの送信」「非同期通信」「DOM操作」はそれぞれ作り終わっているので、後は「テキスト操作」を加えれば組み合わせてハイ終わりでございます。というか、オブジェクト指向ってそういう作り方するんですけどね。
ということで、まずは最後のテキスト操作を司るクラスを作ってみます。
//Objクラス(オブジェクト操作)
Obj = function(obj) {
this.obj=obj;
};
Obj.prototype.find = function(selector) {
return this.obj.find(selector);
};
Obj.prototype.attr_replace = function(selector,param,before,after="") {
var obj=this.find(selector);
obj.attr(param,obj.attr(param).replace(new RegExp(before,'g'),after));
};
//クローンテスト
function Clonetest() {
var clone = new Clone();
clone.copy("list_content_dummy");
clone.paste_before("group_admin");
var c_obj = new Obj(clone.cloned_obj);
c_obj.attr_replace("a","href","dummy","x123");
c_obj.attr_replace("a","data-id","dummy","x123-data");
c_obj.find("a").text("あああああああ");
clone.visible();
}
こんなもの作ってみました。別にクラス化する必要ないんじゃないかと思われるかも知れませんが、これがなかなか使い勝手良さそうだなぁとは思うのです。
どんなクラスかっていうと、受け取ったオブジェクトをごにょごにょするという趣旨のクラスなんですけど、まずコンストラクタで操作したいオブジェクトを紐付けますね。
で、今回はいろんな要素をreplaceで置換したいなという時に、セレクタ、プロパティ、あとは置換前後の文字列(指定しなければ消去の意)を引数にして実行させれば、いい感じに置換してくれるというメソッドになってます。
findという関数名が紛らわしいんですが、c_objはオブジェクトではなくObjクラスなので、jQueryのそれとはまた違う関数となります。最初はfinderみたいな名前にしてたんですが、結局やっぱり紛らわしくなるのでfindにしました(笑)
ということで、これで要素のクローンが一通りできるようになったわけですね。
最後に今まで作ったものを組み合わせて
「管理グループの追加」という一連の流れを作ってみましょう。
以下、思いつきでほぼ全てのコードを公開してみます。実はちょいちょい変えてるところあるんですが、気になる人は探してみてください(笑)(笑)
一つ例を挙げるとすると、クローンクラスをちょっと書き換えて、コンストラクタの時点でコピーまで終わらせることにしてたりね・・・・・・。
@include("dummies")
<!DOCTYPE html>
<html>
<head>
//~省略~//
</head>
<body>
@if(Auth::check())
こんにちは!!{{Auth::user()->name}}さん!!
<hr>
@endif
@yield('content')
<script src="{{url('js/bootstrap.min.js')}}"></script>
<script src="{{url('js/Classes.js')}}"></script>
<script src="{{url('js/functions.js')}}"></script>
@yield("list_content_dummy")
</body>
</html>
@extends('template')
@section('content')
<div>
・管理リスト
<a class="btn btn-default" data-toggle="collapse" href="#group_add">追加</a>
<div id="group_add" class="collapse">
<div style="max-width:480px;">
<form id="group_new_form" action="javascript:group_add_confirm();" method="post">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
グループのタイトル
<input type="text" class="form-control" name="title" value="" placeholder="新規作成するグループの名前を入力して下さい。(最大255文字)">
<div id ="title_alert" class="alert" style="color:#F00;"></div>
管理者名
<input type="text" class="form-control" name="admin_name" value="{{Auth::user()->name}}" placeholder="グループにおける管理者の表示名を入力して下さい。(最大255文字)">
<input type="checkbox" name="no_join">自身をグループに含めない
<div id ="admin_name_alert" class="alert" style="color:#F00;"></div>
<input type="submit" id="group_add_confirm" class="btn btn-info" value="確認">
</form>
</div>
<!-- モーダル・ダイアログ -->
<div class="modal fade" id="group_add_confirm_Modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content" style="text-align:center;">
<div class="title_confirm" style="margin-top:10px;font-weight:bold;"></div>
<hr>
<div class="modal-body">
このグループ名で新規作成しますか?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" onClick="group_add()" data-dismiss="modal">新規作成</button>
<button type="button" class="btn btn-default" data-dismiss="modal">キャンセル</button>
</div>
</div>
</div>
</div>
</div>
<br>
<br>
<b>・管理グループ</b><br>
<div id="group_admin">
@each('list_content', $getlist->list_admin, 'list')
</div>
</div>
<br>
<br>
<div>
<b>・所属リスト</b><br>
<div id="group_join">
@each('list_content_join', $getlist->list_user, 'list')
</div>
</div>
@endsection
<?php
class Dummy
{
public $g_id="dummy";
public $g_title="dummy_title";
}
$dummy[0] = new Dummy;
?>
@section('list_content_dummy')
<div id="list_content_dummy" style="display:none">
@each('list_content', $dummy, 'list')
</div>
<div id="list_content_join_dummy" style="display:none">
@each('list_content_join', $dummy, 'list')
</div>
@endsection
//~省略~(正直sectionなんかで統合したいけどバグりそうだからやめた)//
HTML側の実装は上のような感じですね。続いてプログラム側です。
Route::group(['middleware' => ['web']], function () {
Route::get('/', 'MainController@main');
});
//~認証関係・略~//
Route::group(['middleware' => 'auth'], function()
{
/*認証を通過していない場合ページ遷移を拒否する*/
Route::post('group/new', 'AdminController@new_group');
});
<?php namespace App\Http\Controllers;
use Auth;
use DB;
use App\Http\Controllers\Controller;
require '../app/Http/Controllers/Class.php';
class MainController extends Controller {
public function main()
{
if (!Auth::check()) {
// ユーザーはログインしていない→ログインページへ
return view('index');
}
//ログイン済→データベースからリストを取得
$admin=new AdminUser();
$getlist=new GetList($admin);
return view('list')->with("getlist",$getlist);
}
}
<?php
namespace App\Http\Controllers;
use Auth;
use DB;
class AdminUser
{
/*サンプルプログラム*/
//AdminUser admin(int)
//db $list = admin->group_list()
// プロパティ
public $id;
public $email;
function __construct() {
$this->id=Auth::user()->id;
$this->email=Auth::user()->email;
}
// メソッドの宣言
public function group_list() {
echo $this->id;
}
}
class GetList
{
/*sample*/
//GetList user(str)
//db list = user->get_list()
//GetList admin(int)
//db admin_list = admin->get_list()
// プロパティ
private $id=0;
private $email='';
public $list_admin;//管理グループリスト
public $list_user;//所属グループリスト
function __construct($var) {
if(!is_object($var)){
//渡された値が非オブジェクト(メールアドレス)であれば一般ユーザー
$this->email=$var;
}else{
//渡された値がオブジェクト:ユーザーデータ
$this->id=$var->id;
$this->email=$var->email;
}
$this->get_user();
if(!empty($this->id)){$this->get_admin();}//idが空でない=登録者の場合、管理グループリストも取得
}
public function get_admin(){
//ユーザーIDから管理リスト取得
$this->list_admin = DB::table("administrators")
->where('a_u_id', '=', $this->id)
->join('groups', 'a_g_id', '=', 'g_id')
->orderBy('groups.update_at', 'desc')
->get();//管理グループのリスト
}
public function get_user(){
//メールアドレスから所属グループリスト取得
$this->list_user = DB::table("lists")
->where('l_email', $this->email)
->join('groups', 'l_group', '=', 'g_id')
->orderBy('groups.update_at', 'desc')
->get();//所属グループのリスト
}
}
<?php
//~省略~//
public function rules()
{
return [
'new_title' => 'required|max:255',
'admin_name' => 'required|max:255',
];
}
}
<?php namespace App\Http\Controllers;
use Auth;
use DB;
use Illuminate\Http\Request;
use App\Http\Requests\NewGroupRequest;
use App\Http\Controllers\Controller;
require '../app/Http/Controllers/Class.php';
class AdminController extends Controller {
public function new_group(NewGroupRequest $request)
{
//@param
//String $request["new_title"]=新タイトル
//String $request["admin_name"]=管理者名
//Bool $request["no_join"]=trueの場合、自身を含めない
$new_g_id=null;
//データベース読み出し
try {
DB::transaction(function () use($request) {
$this->new_g_id = DB::table('groups')->insertGetId([
'g_title' => $request["new_title"],
'exec_admin' => $request["admin_name"],
'exec_id' => Auth::user()->id
]);
DB::table('administrators')->insert([
'a_u_id' => Auth::user()->id,
'a_g_id' => $this->new_g_id
]);
if($request["no_join"]!="true"){
DB::table('lists')->insert([
'l_email' => Auth::user()->email,
'l_name' => $request["admin_name"],
'l_group' => $this->new_g_id
]);
}
});
return json_encode(["id"=>$this->new_g_id]);
}catch(Exception $e){
}
}
}
最後にjavascriptです。
//グループ追加確認の親関数
function validation(str){
str=str.replace(/[\n\r]/g, '');//改行を除去
sanitaized_str=sanitaize.encode(str);
if(sanitaized_str.length>255){return "over";}//文字数超過
if(!blank_validate(sanitaized_str)){return "blank";}//空白改行のみ
return true;
}
//バリデーションの結果を返す関数
function group_add_function(){
var title=$('#group_new_form [name=title]').val();//入力されたタイトル取得
var admin_name=$('#group_new_form [name=admin_name]').val();//入力された名前取得
var is_complete=true;
var array=[['title_alert',title],['admin_name_alert',admin_name]];
$.each(array, function(a, val) {
var flag=validation(array[a][1]);
if(flag!==true){
switch(flag){
case "over":
$('#'+array[a][0]).text("文字数が多すぎます(255文字以下にして下さい。)");break;
case "blank":
$('#'+array[a][0]).text("入力が空白です。");break;
}
is_complete=false;
}else{
$('#'+array[a][0]).text("");
}
});
if(!is_complete){return false;}
//バリデーション通過
return true;
}
function group_add_confirm(){
//バリデーション
if(group_add_function()===false){return false;}
//モーダル起動
$("#group_add_confirm_Modal").find(".title_confirm").html(sanitaize.encode($('#group_new_form [name=title]').val()));
$("#group_add_confirm_Modal").find(".modal-dialog").show();
$("#group_add_confirm_Modal").modal();
}
//グループ追加実行
function group_add(){
if(group_add_function()===false){return false;}
//配列取得
var data_array={
new_title:$('#group_new_form [name=title]').val(),
no_join:$('#group_new_form [name=no_join]').prop('checked'),
admin_name:$('#group_new_form [name=admin_name]').val()
};
var ajax = new Ajax("./group/new", data_array ,"group_add");
ajax.ajaxsend();
}
//サニタイズ用
sanitaize = {
encode : function (str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
},
decode : function (str) {
return str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '\'').replace(/&/g, '&');
}
};
function blank_validate(str){
return !(/^[ \n\r]*$/.test(str));
}
//Objクラス(オブジェクト操作)
Obj = function(obj) {
this.obj=obj;
};
Obj.prototype.find = function(selector) {
return this.obj.find(selector);
};
Obj.prototype.attr_replace = function(selector,param,before,after="") {
var obj=this.find(selector);
obj.attr(param,obj.attr(param).replace(new RegExp(before,'g'),after));
};
//Cloneクラス(要素コピー)
Clone = function(origin_id) {
//sample
//new Clone()->この時点でコピーを生成する
this.origin_id = origin_id;//親要素のID
this.cloned_obj = $("#"+this.origin_id+"").clone(true);
this.cloned_obj.removeAttr("id");//IDを消去
};
Clone.prototype.paste_before = function(parent_id, insertBefore=null) {
//insertBefore ->obj :そのノードの前に挿入
// ->null :先頭に挿入
this.parent_id = parent_id;
if(!insertBefore){insertBefore=$("#"+this.parent_id+" div:first");}
this.cloned_obj.insertBefore(insertBefore);
};
Clone.prototype.visible = function() {
this.cloned_obj.css('display', '');
};
Clone.prototype.set_to_parent = function(to,data,ajax_obj,insertBefore=null) {
//param
//to->親要素のID
//data->ajaxから受け取ったデータ
//ajax_obj->Ajaxインスタンス
this.paste_before(to,insertBefore);
this.obj = new Obj(this.cloned_obj);
this.obj.attr_replace("a","href","dummy",data["id"]);
this.obj.attr_replace("a","data-id","dummy",data["id"]);
this.obj.find("a").html(sanitaize.encode(ajax_obj.data_array["new_title"]));
this.visible();
};
//Ajaxクラス
Ajax = function(url, data_array, success, error = "default_error") {
// this はインスタンスを表します。
this.url = url;
this.data_array = data_array;
this.success = success;
this.error = error;
};
Ajax.prototype.ajaxsend = function() {
$.ajax({
type: "POST",
url: this.url,
data:this.data_array,
beforeSend: function (xhr) {
var token = $('meta[name="csrf_token"]').attr('content');
if (token) {
return xhr.setRequestHeader('X-CSRF-TOKEN', token);
}
}
})
.then(
(data) => {
this[this.success](data);
},
() => {
this[this.error]();
}
);
};
//Success
//グループ追加成功
Ajax.prototype.group_add = function(data) {
data=JSON.parse(data);
if(!isFinite(data["id"])){return false;}//一応数値チェック
var clone = new Clone("list_content_dummy");
clone.set_to_parent("group_admin",data,this);
if(this.data_array["no_join"]===true){return true;}
var clone_join = new Clone("list_content_join_dummy");
clone_join.set_to_parent("group_join",data,this);
}
//error
var error=[];
Ajax.prototype.default_error = function() {
alert("通信に失敗しました。もう一度やり直して下さい。");
}

この約500行程度のプログラムで、グループの表示と新規作成コマンドが成り立つわけですね。これでやっとこさ一段落つきました。
デザインが全く考えられてないのでGUIはクソなんですけど、まぁそんなのは後からいくらでも修正できますしね。
ここからグループの個別編集にやっと移ることができます。
グループのメンバー追加・削除だったり、メンバーの加入・脱退まわり、そして肝となる出欠調査とかその辺りを作ることになりますね。
日記に取り上げる話があれば取り上げる予定ではいますが、こんな風にコードを全部見せるみたいなのはたぶんもうやらないです(笑)(笑)まぁこんなもん書いてるんですよっていうのを見せた方がいいかなってね。