Webixライブラリを使ってWebアプリを開発する手順を複数回にわたって説明します。(その2)No.042
前回の続きです。今回は、作業日報を管理するために必要なデータベースの作成と一覧検索などのPHPサーバソースを紹介します。
作業日報情報を保存するデータベースですが、使用するデータベースは、Linuxサーバ(Alumalinx)では、MariaDB、クラウドのさくらインターネットレンタルサーバでは、MySQLを使いますが、互換性があることと、PHP言語からはPDOでのアクセスとなりますので、ソース互換があります。
テーブル名は、dailyreportsとしました。
キーはidとし、自動採番です。(INSERTするごとに自動的に番号が付与されるもの)
CREATE TABLE `dailyreports` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id:自動連番',
`user_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'ユーザID',
`report_date` date DEFAULT NULL COMMENT '日付',
`work_place` varchar(255) NOT NULL DEFAULT '' COMMENT '作業場所',
`work_details` varchar(255) NOT NULL DEFAULT '' COMMENT '作業内容',
`start_time` time DEFAULT NULL COMMENT '開始時刻',
`end_time` time DEFAULT NULL COMMENT '終了時刻',
`duration_hour` time DEFAULT NULL COMMENT '経過時間',
`photo_name` varchar(255) NOT NULL DEFAULT '' COMMENT '写真ファイル名',
`photo_folder` varchar(255) NOT NULL DEFAULT '' COMMENT '写真フォルダ名',
`workers` int(4) NOT NULL DEFAULT 0 COMMENT '作業人数',
`memo` varchar(255) NOT NULL DEFAULT '' COMMENT 'メモ',
`latitude` varchar(255) NOT NULL DEFAULT '' COMMENT '緯度',
`longitude` varchar(255) NOT NULL DEFAULT '' COMMENT '経度',
`created_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '作成ユーザ',
`updated_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '更新ユーザ',
`created_on` datetime DEFAULT NULL COMMENT '作成日時',
`updated_on` datetime DEFAULT NULL COMMENT '更新日時',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='日報';
以下のフィールドは、共通的に付与しています。
`created_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '作成ユーザ',
`updated_userid` varchar(16) NOT NULL DEFAULT 'admin' COMMENT '更新ユーザ',
`created_on` datetime DEFAULT NULL COMMENT '作成日時',
`updated_on` datetime DEFAULT NULL COMMENT '更新日時',
クラウドのさくらインターネットレンタルサーバでは、phpMyAdminツールが提供されているので、そのツールで使用するデータベースのインスタンスにログインした状態で、上記のSQL文を実行すれば、テーブルを作成できます。自宅などで、Linuxサーバ上にMariaDBを構築した場合もphpMyAdminツールをインストールすることも可能ですが、簡単なadminer.phpで同様の操作ができるので、adminer.phpを使っています。(1つのPHPソースで構築できるので、簡単です)
データベースのテーブルを作成したら、まずは、検索用のREST APIを作成し、関連する画面を作っていきます。
画面には検索条件を指定するコンポーネントを実装することを考慮して、画面とサーバ間の検索用のパラメータ情報を決めます。
とりあえず、以下の情報が指定された場合は、AND条件で検索するように実装します。
検索パラメータ
fl_user_id 対象ユーザを絞るパラメータ(ログインしたユーザの作業日報を表示する実装からスタートするので、画面上では、変更できない実装)
fl_start_report_date 対象作業日報を絞るパラメータ(開始日)
fl_end_report_date 対象作業日報を絞るパラメータ(終了日)
一覧表に表示する情報
id レコードID(非表示)
user_id ユーザID(非表示)
report_date 日付
work_place 作業場所
work_details 作業内容
duration_hour 経過時間
id とuser_idはは、DBから検索して画面に渡すが、画面には非表示(内部で保持するだけ)とする
前回の記事でも記載した機能をグループ化するための機能IDとして、DR0010とします。
一覧画面は、/view/DR0010/DR0010_dailyreport_lists.php
検索用のREST_APIは、/rest_api/DR0010/DR0012_dailyreport_selectlists.phpとします。
以下、検索用のソースです。
DR0012_dailyreport_selectlists.php
<?php
//======================================================================
//File Name : DR0012_dailyreport_selectlists.php
//Encoding : UTF-8
//Creation Date : 2024-06-18
//
//Copyright © 2024 sunsunfarm. All rights reserved.
//
//This source code or any portion thereof must not be
//reproduced or used in any manner whatsoever.
//
// usage example /rest_api/DR0010/DR0012_dailyreport_selectlists.php?userid=admin&fl_user_id=admin&fl_start_report_date=2024-06-18&fl_end_report_date=2024-06-18
//======================================================================
//header("Content-Type: text/javascript; charset=utf-8");
$userid ='';
$myfilename = basename(__FILE__); //自分自身のファイル名取得
$logheader = 'log:'.$myfilename.' userid='.$userid.' :';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
if($_SERVER["REQUEST_METHOD"] != "GET"){
//GET以外ははじく
error_log($logheader.' REQUEST_METHOD no GET');
header("HTTP/1.0 404 Not Found");
return;
}
//$accesskey =0;
//GET情報からパラメータ取得
//if(isset($_GET['accesskey'])){
// if(is_numeric($_GET['accesskey'])){
// $accesskey = intval($_GET['accesskey']);
// }
//}
//else{
// $error_flag = -1;
// error_log($logheader.' accesskey not found');
// header("HTTP/1.0 404 Not Found");
// exit;
//}
if(isset($_GET['userid'])){
$userid = $_GET['userid'];
}
else{
$error_flag = -1;
error_log($logheader.' userid not found');
header("HTTP/1.0 404 Not Found");
exit;
}
$logheader = 'log:'.$myfilename.' userid='.$userid.' :';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
include('../../commonlib/svr_common_lib_v2.php'); //
//検索条件用パラメータ
$fl_user_id ='';
$fl_start_report_date='';
$fl_end_report_date='';
if(isset($_GET['fl_user_id'])){
$fl_user_id = $_GET['fl_user_id'];
}
if(isset($_GET['fl_start_report_date'])){
$fl_start_report_date = $_GET['fl_start_report_date'];
}
if(isset($_GET['fl_end_report_date'])){
$fl_end_report_date = $_GET['fl_end_report_date'];
}
error_log($logheader.' $fl_user_id ='.$fl_user_id);
error_log($logheader.' $fl_start_report_date ='.$fl_start_report_date);
error_log($logheader.' $fl_end_report_date ='.$fl_end_report_date);
$config_obj = get_config_obj();
//アクセスキーチェック
//if(Chk_AccessKey($accesskey)){
// $error_flag = 1;
//}
//else{
// error_log($logheader.' accesskey check error');
// header("HTTP/1.0 404 Not Found");
// exit;
//}
//
//メインルーチン
//
//データベース接続する(MySQLDB)
$dbh = mariadb_connect($config_obj,'app01','webix_webix'); //configファイルに従って、DB接続して応答を返す
if($dbh == false){
//エラー応答
$resp = "ng";
$count = 0;
$error_code = -1;
$var_lists = array();
$json_data = json_encode(compact("resp","error_code"),JSON_UNESCAPED_UNICODE);
echo $json_data;
exit;
}
try {
$select_sql = 'SELECT id as report_id,user_id,report_date,work_place,work_details,duration_hour from dailyreports ';
$where_str_array = array();
if($fl_user_id != ""){
$where_str_array[] =" user_id = '".$fl_user_id."'";
}
if($fl_start_report_date != ""){
$where_str_array[] =" report_date >= '".$fl_start_report_date."'";
}
if($fl_end_report_date != ""){
$where_str_array[] =" report_date <= '".$fl_end_report_date."'";
}
if(count($where_str_array) > 0){
$where_str = implode(" and ",$where_str_array);
$select_sql = $select_sql." where ".$where_str;
}
$select_sql = $select_sql.' order by report_date,start_time,user_id ';
//作成したSQL文をログに出力
error_log($logheader.' sql='.$select_sql);
$stmt1 = $dbh->prepare($select_sql);
$stmt1->execute();
$var_lists = $stmt1->fetchAll(PDO::FETCH_ASSOC); //フィールド名だけで保存(index情報なし)
$count = count($var_lists);
error_log($logheader.' select dailyreports count ='.$count );
for($i=0;$i<$count;$i++){
$var_lists[$i]["id"] = strval($i+1);
}
$resp = "ok";
$error_code = 0;
}catch (Exception $e) {
$resp = "ng";
$count = 0;
$error_code = -1;
}
//後処理
$stmt = null;
$dbh = null; //DBクローズ
$json_data = json_encode(compact("resp","error_code","count","var_lists"),JSON_UNESCAPED_UNICODE);
echo $json_data;
?>
動作検証は、画面作成まで待つか、画面の代わりにpostmanを使用する方法があります。以下は、postmanで対象APIをコールしたときの応答です。
実際には、レコードがまだないので、0件で応答となります。
PHPのログは、以下のように出力されます。
[18-Jun-2024 15:40:34 Asia/Tokyo] log:DR0012_dailyreport_selectlists.php userid=admin : $fl_user_id =admin
[18-Jun-2024 15:40:34 Asia/Tokyo] log:DR0012_dailyreport_selectlists.php userid=admin : $fl_start_report_date =2024-06-18
[18-Jun-2024 15:40:34 Asia/Tokyo] log:DR0012_dailyreport_selectlists.php userid=admin : $fl_end_report_date =2024-06-18
[18-Jun-2024 15:40:34 Asia/Tokyo] log:DR0012_dailyreport_selectlists.php userid=admin : sql=SELECT id as report_id,user_id,report_date,work_place,work_details,duration_hour from dailyreports where user_id = 'admin' and report_date >= '2024-06-18' and report_date <= '2024-06-18' order by report_date,start_time,user_id
[18-Jun-2024 15:40:34 Asia/Tokyo] log:DR0012_dailyreport_selectlists.php userid=admin : select dailyreports count =0
レコードをINSERTしないと正しく動作しているかも判断しにくいので、画面の作成に入ります。
まずは、一覧を表示する画面を実装し、新規追加ボタンを追加していきます。(デバックはパソコン画面の方がやりやすいので、パソコン用の一覧kだら作成します。)
作業日報一覧のPC版の画面デザインです。
検索条件は、作業日報の開始日と終了日を指定します。
デフォルトは、終了日が当日で、開始日は、同月の初日としました。
日付は、カレンダで指定しますが、開始日、終了日、当日の日付間で矛盾が生じる指定をすると、検索のボタンを無効にして、メッセージで正しくない指定であることを伝えるようにしました。
開始日が、終了日より未来日の場合など
検索ボタンがクリックされると、検索条件の値を画面から取り出し、サーバに送信します。
今回は、対象ユーザIDと開始日、終了日です。
var start_date = moment($$("fl_start_report_date").getValue()).format("YYYY/MM/DD");
var end_date = moment($$("fl_end_report_date").getValue()).format("YYYY/MM/DD");
var access_key = Get_AccessKey();
var send_prm = Prepare_send_prm(my_local_session, access_key);
send_prm.fl_user_id = fl_user_id;
send_prm.fl_start_report_date = start_date;
send_prm.fl_end_report_date = end_date;
var xhr =webix.ajax().sync().get("<?php echo SUB_FOLDER; ?>/rest_api/DR0010/DR0012_dailyreport_selectlists.php",send_prm);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
$$("DR0010_table").parse(resp.var_lists);
var v_count =$$("DR0010_table").count();
$$("select_count").setValue(String(v_count));
if(v_count == 0 ){
$$("comment").setValue("検索結果は、0件です。");
webix.message({type:"debug", text:"検索結果は、0件です。"});
}
else{
$$("comment").setValue("");
}
}
else{
webix.message({type:"error", text:"検索でエラーが発生しました。code="+resp.error_code});
}
サーバ側は、先に作成したPHPのREST_API /rest_api/DR0010/DR0012_dailyreport_selectlists.phpです
画面全体のソースDR0010_dailyreport_lists.phpは、以下のとおりです。
<?php
//======================================================================
//File Name : DR0010_dailyreport_lists.php
//Encoding : UTF-8
//Creation Date : 2024-06-18
//
//Copyright © 2024 sunsunfarm. All rights reserved.
//
//This source code or any portion thereof must not be
//reproduced or used in any manner whatsoever.
//======================================================================
$TITLE_INFO ="作業日報一覧";
$VER_INFO ="V01L01";
$JOB_INFO = "DR0010";
$myfilename = basename(__FILE__); //自分自身のファイル名取得
define('SUB_FOLDER','/webix02'); //サブフォルダを指定したURL
define('ROOT_PATH','/home/sunsun/www/webix02'); //ソースを保存しているパス(動作環境に応じて記述する必要あり)
$userid = '';
$logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
if($_SERVER["REQUEST_METHOD"] != "GET"){
error_log($logheader.' REQUEST_METHOD no GET');
header("HTTP/1.0 404 Not Found");
return;
}
$error_flag = -1;
$accesskey = 0;
$userclient ="pc";
$mypermission = 0;
if(isset($_GET['accesskey'])){
if(is_numeric($_GET['accesskey'])){
$accesskey =$_GET['accesskey'];
$error_flag = 1;
}
}
else{
$error_flag = -1;
error_log($logheader.' accesskey not found');
header("HTTP/1.0 404 Not Found");
exit;
}
if(isset($_GET['userid'])){
$userid = $_GET['userid'];
$error_flag = 1;
}
else{
$error_flag = -1;
error_log($logheader.' userid not found');
header("HTTP/1.0 404 Not Found");
exit;
}
if(isset($_GET['userclient'])){
$userclient = $_GET['userclient'];
}
if(isset($_GET['permission'])){
if(is_numeric($_GET['permission'])){
$mypermission = intval($_GET['permission']);
}
}
$logheader = 'userid='.$userid.', '.$myfilename.':';//ログ出力時のヘッダー情報(自ファイル名,ログインIDを付与)
include('../../commonlib/svr_common_lib_v2.php'); //
$config_obj = get_config_obj();
if(Chk_AccessKey($accesskey)){
}
else{
error_log($logheader.' accesskey check error');
header("HTTP/1.0 404 Not Found");
exit;
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title><?php echo $TITLE_INFO.' DR0010 ('.$VER_INFO.')' ?></title>
<script src="/webix_GPL_1020/webix.js" type="text/javascript" charset="utf-8"></script>
<link href="/webix_GPL_1020/skins/compact.css?<?php echo date('Ymd-H'); ?>" rel="stylesheet" type="text/css">
<link href="<?php echo SUB_FOLDER; ?>/commonlib/webix_custom_css.css?<?php echo date('Ymd-H'); ?>" rel="stylesheet" type="text/css">
<link rel="icon" href="<?php echo SUB_FOLDER; ?>/image/webix_64.ico">
<script src="<?php echo SUB_FOLDER; ?>/commonlib/object-assign.js"></script>
<script src="<?php echo SUB_FOLDER; ?>/commonlib/moment-with-locales.js"></script>
<script src="<?php echo SUB_FOLDER; ?>/commonlib/webix_common_lib.js"></script>
<link rel="stylesheet" href="/webix_GPL_1020/css/materialdesignicons.min.css" type="text/css" charset="utf-8">
<style>
.blue .webix_el_box{
color: #0000ff;
}
.blue .webix_control.webix_el_text{
color: #0000ff;
}
.red .webix_el_box{
color: #ff0000;
}
.red .webix_control.webix_el_text{
color: #ff0000;
}
.invalid_mess{font-size:8px}
.red .webix_control.webix_el_text{
color: #ff0000;
}
.orange_bgcolor button.webix_button{
background: #FF8856;
color:#FFFFFF;
border:1px solid #FF8856;
}
.gray_bgcolor {
background: #efefef;
}
.highlight{
background-color:#FFAAAA;
}
.teal_bgcolor .webix_button {
background-color: #008080;
color: #FFFFFF;
}
.green input{
background-color:#B1FF91;
border-color:green;
}
.pink input{
background-color:#FFB6FE;
border-color:red ;
}
</style>
</head>
<body>
<script type="text/javascript" charset="utf-8">
webix.i18n.setLocale("ja-JP");
var my_local_session = webix.storage.local.get('login');
<?php
include_once('../../commonlib/CM0010_goto_menu_action.php');
include_once('../../commonlib/CM0030_sendprm_set_request.php');
include_once('../../commonlib/CM0060_common_validate_check.php');
include_once('../../commonlib/CM0070_prompt_parameter_setting.php');
include_once('../../commonlib/CM0080_screen_control.php');
if(isset($_GET['open_mode'])){
$open_mode = $_GET['open_mode'];
if($open_mode == '_blank'){
echo ' var menu_btn_name = "閉じる";'."\n";
}
else{
echo ' var menu_btn_name = "メニュー";'."\n";
}
}
else{
echo ' var menu_btn_name = "メニュー";'."\n";
}
echo ' var my_permission ='.$mypermission.";\n";
?>
<?php
if($userclient != "pc"){
?>
webix.ui.fullScreen();
<?php
}
?>
var fl_start_report_date = moment().startOf('month').format("YYYY/MM/DD");
var fl_end_report_date = moment().format("YYYY/MM/DD");
webix.i18n.setLocale("ja-JP");
function DR0010_select_dailyreport_lists(fl_user_id){
$$("DR0010_table").clearAll(); //リストを一度クリア
CM0080_datatable_filter_clear("DR0010_table");
var start_date = moment($$("fl_start_report_date").getValue()).format("YYYY/MM/DD");
var end_date = moment($$("fl_end_report_date").getValue()).format("YYYY/MM/DD");
var access_key = Get_AccessKey();
var send_prm = Prepare_send_prm(my_local_session, access_key);
send_prm.fl_user_id = fl_user_id;
send_prm.fl_start_report_date = start_date;
send_prm.fl_end_report_date = end_date;
var xhr =webix.ajax().sync().get("<?php echo SUB_FOLDER; ?>/rest_api/DR0010/DR0012_dailyreport_selectlists.php",send_prm);
var resp = JSON.parse(xhr.responseText);
if(resp.resp =="ok"){
$$("DR0010_table").parse(resp.var_lists);
var v_count =$$("DR0010_table").count();
$$("select_count").setValue(String(v_count));
if(v_count == 0 ){
$$("comment").setValue("検索結果は、0件です。");
webix.message({type:"debug", text:"検索結果は、0件です。"});
}
else{
$$("comment").setValue("");
}
}
else{
webix.message({type:"error", text:"検索でエラーが発生しました。code="+resp.error_code});
}
}
//検索条件フォーム構成リスト
var form_collection = [
{ margin:5,
cols:[
{ view:"label", height:50, template:"<span style='font-weight:bold; font-size:180%;'>作業日報一覧</span>",width:890},
]
},
{ margin:5,
cols:[
{view:"datepicker",id:"fl_start_report_date",name:"fl_start_report_date",label:'開始日',labelAlign:"right",value:fl_start_report_date,width:"200",type: "date",format:webix.Date.dateToStr("%Y/%m/%d")
,on:{
onChange:function(newv,oldv){
var end_date = moment($$("fl_end_report_date").getValue()).format("YYYYMMDD");
var start_date = moment(newv).format("YYYYMMDD");
var today = moment().format("YYYYMMDD");
if(start_date > today ){
$$("DR0010_search_btn").disable();
webix.message({type:"error",text:"日付の範囲が<br>正しくありません。"});
}
else if(end_date < start_date){
$$("DR0010_search_btn").disable();
webix.message({type:"error",text:"日付の範囲が<br>正しくありません。"});
}
else{
$$("DR0010_search_btn").enable();
}
},
onEnter(ev){
$$("DR0010_search_btn").focus();
}
}
},
{view:"datepicker",id:"fl_end_report_date",name:"fl_end_report_date",label:'終了日',labelAlign:"right",value:fl_end_report_date,width:"200",type: "date",format:webix.Date.dateToStr("%Y/%m/%d"),
on:{
onChange:function(newv,oldv){
var start_date = moment($$("fl_start_report_date").getValue()).format("YYYYMMDD");
var end_date = moment(newv).format("YYYYMMDD");
var today = moment().format("YYYYMMDD");
if(end_date > today ){
$$("DR0010_search_btn").disable();
webix.message({type:"error",text:"日付の範囲が<br>正しくありません。"});
}
else if(end_date < start_date){
$$("DR0010_search_btn").disable();
webix.message({type:"error",text:"日付の範囲が<br>正しくありません。"});
}
else{
$$("DR0010_search_btn").enable();
}
},
onEnter(ev){
$$("DR0010_search_btn").focus();
}
}},
{ view:"button", value: "検索", align:"center", width: 110,id:"DR0010_search_btn",name:"DR0010_search_btn",
click:function(){
var fl_user_id = my_local_session.userid;
DR0010_select_dailyreport_lists(fl_user_id);
}
},
// メニューに戻るボタン実装
{ view:"button", value: menu_btn_name, align:"center", width: 110, css:"menu",
click:function(){
Goto_Menu(my_local_session["userclient"]); //メニュー画面へ遷移
}
}
]
},
];
var grid1 = {
id: "DR0010_table",
name: "DR0010_table",
type:"clean",
view:"datatable",
width:800,
resizeColumn:true,
resizeRow: { headerOnly:true },
columns:[
{ id:"id",header:["id",{content:"textFilter"}],width:60,sort:"int", css:{"text-align":"right"}},
{ id:"report_id",header:["report_id"],width:100,sort:"int",hidden:true},
{ id:"user_id",header:["user_id"],width:100,sort:"int",hidden:true},
{ id:"report_date",header:["日付",{content:"textFilter"}],width:100,sort:"int", css:{"text-align":"right"}},
{ id:"work_place",header:["作業場所'",{content:"selectFilter"}],width:200,sort:"string"},
{ id:"work_detail",header:["作業内容'",{content:"selectFilter"}],width:200,sort:"string"},
{ id:"duration_hour",header:["経過時間'",{content:"selectFilter"}],width:100,sort:"int"},
],
// 選択
on:{
onItemClick:function(id){
var item = this.getItem(id);
DR0011_disp_win_show(my_permission,my_local_session,item.code);
}
},
resizeColumn:true, resizeRow:true,
leftSplit:2,
scroll:"xy",
select:"row",
datatype:"json",
data:[]
};
//画面描画
webix.ui({
padding: 10,
rows:[ { view:"form", id: "form1", elements : form_collection, margin:3, select:true },
{ margin:5,cols: [
{ view:"text", label:"検索結果(件)", labelWidth:100,labelAlign:"right",name:"select_count",id:"select_count",value:0,width:160, inputAlign:"right",readonly:true },
{ view:"label",label:"", width:300, name:"comment", id:"comment" }
]},
grid1,
]
});
</script>
</body>
</html>
このソースは、メニューから機能を選択して実行する仕組みで動くようにしていることや、ログイン後に画面間で情報を共有するための仕組みなども使っているので、my_local_sessionやmy_permissionなどの変数が存在します。
各ソースは、不正アクセスを防止するための仕組みも実装しており、指定アクセスキーが正しくない場合は、エラー応答になります。
従って、今回紹介しているソースをコールしても正しく動作しません。ログイン操作をしてメニューから作業日報をクリックしないと動作しません。
以下のようなメニューから機能を選択して画面を開きます。
今回は、ここまでの紹介です。
次回、新規作成ボタンで作業日報を追加する機能を説明します。