トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

mmt -- Master Maintenance Tool perl module.

目的

mmt.pmをmojoliciousで書き直す。

  • apache無しでmojoliciousたけで動かす。(apache,psgi,等々で動かしても良いが…)
  • utf-8をデフォルトにする。
  • prototype.jsからjQueryに移行する。

github

https://github.com/john-smith-7701/mmt に登録した。

  • インストール
$ git clone https://github.com/john-smith-7701/mmt.git

memo

初めの一歩

$ mojo generate app Tool::mmt
$ tree toolmmt
toolmmt
├── lib
│     └── Tool
│            ├── mmt
│            │     └── Example.pm
│            └── mmt.pm
├── log
├── public
│     └── index.html
├── script
│     └── toolmmt
├── t
│     └── basic.t
└── templates
       ├── example
       │     └── welcome.html.ep
       └── layouts
              └── default.html.ep

10 directories, 7 files
$ svn import toolmmt svn+ssh://userId@localhost/usr/local/svn/repos/toolmmt -m "Initial Import."
$ svn co svn+ssh://userId@www21051ue.sakura.ne.jp/usr/local/svn/repos/toolmmt

2015/11/10現在

$ tree -f |perl -alne '@x=split /\s+/,`wc -l $F[-1] 2>/dev/null`;$l=sprintf("%5d %s",$x[0],$_);$l=~ s/ 0 /   /;$l=~ s{\..*/}{};print $l'
      .
      ├── lib
      │   └── Tool
      │       ├── mmt
   13 │       │   ├── Example.pm
  512 │       │   └── Mmt.pm
   25 │       ├── mmt.pm
      │       ├── Model
   46 │       │   └── Webdb.pm
    7 │       └── Model.pm
      ├── log
 3979 │   └── development.log
      ├── public
   11 │   └── index.html
      ├── script
   11 │   └── toolmmt
    4 ├── svn-commit.tmp
      ├── t
    9 │   └── basic.t
      └── templates
          ├── example
    7     │   └── welcome.html.ep
          ├── layouts
    5     │   └── default.html.ep
          └── mmt
   27         ├── datalist.html.ep
    5         ├── desc.html.ep
   29         └── mainform.html.ep
      
      12 directories, 15 files
$

2015/11/21

とりあえず素のメンテ作成。cssも JavaScript (12/9) も無し。

[2017/04/09]帳票悪性ツール(rwt)追加

-- https access

[2016/01/11]

     .
     ├── lib
     │   └── Tool
     │       ├── Model
     │       │   ├── Webdb
  29 │       │   │   └── constant.pm
 210 │       │   └── Webdb.pm
   7 │       ├── Model.pm
     │       ├── mmt
  35 │       │   ├── Commodity.pm
  13 │       │   ├── Example.pm
 759 │       │   ├── Mmt.pm
  19 │       │   └── Usertbl.pm
  28 │       └── mmt.pm
     ├── log
     ├── public
     │   ├── css
 121 │   │   └── default.css
  11 │   ├── index.html
     │   └── js
     ├── script
  11 │   └── toolmmt
   4 ├── svn-commit.tmp
     ├── t
   9 │   └── basic.t
     └── templates
         ├── example
   7     │   └── welcome.html.ep
         ├── layouts
  33     │   ├── default.html.ep
  32     │   └── defsubwin.html.ep
         └── mmt
  36         ├── datalist.html.ep
   9         ├── desc.html.ep
   4         ├── dumper.html.ep
  53         ├── mainform.html.ep
  32         └── subwin.html.ep
     
     15 directories, 21 files

2017/4/9

帳票作成ツールを追加

構成

script/toolmmt # アプリケーションスクリプト

#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../lib" }

# Start command line interface for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Tool::mmt');

lib/Tool/mmt.pm # アプリケーションクラス(ルーター等)

package Tool::mmt;
use Mojo::Base 'Mojolicious';
use Tool::Model;

has 'model' => sub {Tool::Model->new};    # modelを追加
has 'controller' => "mmt";

# This method will run once at server start
sub startup {
  my $self = shift;

  # Documentation browser under "/perldoc"
  $self->plugin('PODRenderer');
  $self->plugin('TagHelpers');
  # Router
  my $r = $self->routes;
  # Normal route to controller
  $r->get('/')->to('example#welcome');
  $r->get('/mmt/:_table/desc')->to('mmt#desc');
  $r->get('/mmt/:_table')->to(controller => $self->controller,action =>'mainform');
  $r->post('/mmt/:_table')->to(controller => $self->controller,action => 'registry');
  $r->get('/mmtx/:controller')->to(controller => $self->controller,action =>'mainform');  # 特別なプレースフォルダ :controllerと:action 柔軟なルーティングの構築が可能になる
  $r->post('/mmtx/:controller')->to(controller => $self->controller,action => 'registry');
}

1;

lib/Tool/Model.pm

package Tool::Model;
use Mojo::Base 'Mojolicious';
use Tool::Model::Webdb;
has 'webdb' => sub { Tool::Model::Webdb->new };   # 自作モデルとか
1;

lib/Tool/Model/Webdb.pm #モデル (DB CONNECT とか…)

package Tool::Model::Webdb;
use Mojo::Base 'Mojolicious';
use DBI;
use utf8;
use Data::Dumper;
use Tool::Model::Webdb::constant;

has const => sub {
    my $s = shift;
    return Webdb::constant->new();  # constant dataを読み込む
};

has dbh => sub {
  my $self = shift;

  my $data_source = $self->const->{data_source};
  my $user        = $self->const->{user};
  my $password    = $self->const->{password};

  my $dbh = DBI->connect(
     $data_source,
     $user,
     $password,
     {RaiseError => 1,
      mysql_enable_utf8 =>1,
      mysql_auto_reconnect =>1,    # 再接続させる
     }
  );
  $dbh->do("set names UTF8");
  return $dbh;
};

sub desc_table{
  my $s = shift;
  my $m = shift || 'm';
  my $f;
  $s->{'m'}->{key} = [];
  $s->{'m'}->{item} = [];
  my $dbh = $s->dbh;
  my $sth = $dbh->prepare($s->desc_param($m));
  $sth->execute();
  my $flag = 0;
  while(my $ref = $sth->fetchrow_hashref()){
       $f = $ref->{Field};
       $s->{'m'}->{$f}->{Type} = $ref->{Type};
       $s->{'m'}->{$f}->{Null} = $ref->{Null};
       $s->{'m'}->{$f}->{Key} = $ref->{Key};
       $s->{'m'}->{$f}->{Default} = $ref->{Default};
       $s->{'m'}->{$f}->{Extra} = $ref->{Extra};
       $s->{'m'}->{$f}->{Size} = $s->size($ref->{Type});
       if($ref->{Key} eq 'PRI'){
           push @{$s->{'m'}->{key}},$f;
       }elsif($ref->{Type} =~ /timestamp/){
           $s->{'m'}->{timestamp} = $f;
       }else{  push @{$s->{'m'}->{item}},$f;
       }
       $flag = 1;
   }
   $sth->finish();
   return $s->{'m'};
}
sub size { . . . }
 .
 .
1;

lib/Tool/Model/Webdb/constant.pm # コンスタントデータ

package Webdb::constant;
use utf8;

sub new {
    my $class = shift;
    my $self = bless {},$class;
    $self->_initalize;
    return $self;
}
sub _initalize{
    my $s = shift;
    $s->{data_source} = "DBI:mysql:database=YourDB;host=localhost";
    $s->{user}        = "YourName";
    $s->{password}    = "YourPassword";

    $s->{explan} = {                            # DB項目の説明
        '担当者'    => {                        #   TABLE NAME
            'ID'    => 'admin:管理者,0〜999',   #    FIELD NAME => COMMENT
        },
        '商品'      => {
            '商品区分' => '0:課税,1:非課税,2:軽減課税',
        },
    };
}

1;

lib/Tool/mmt/Mmt.pm # controller

package Tool::mmt::Mmt;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Log;
use utf8;
use Encode;
use Text::CSV::Encoded;

has 'mmtForm' => 'mmt/mainform';
has 'mmtDataList' => 'mmt/datalist';

# This action will render a template
sub mainform {
    my $self = shift;

    $self->{'m'} = $self->app->model->webdb->desc_table($self->param('_table'));      # model 呼び出し
    $self->{'m'}->{'table'} = $self->param('_table');
    $self->set_input_names();
    $self->action_set();

    $self->my_render($self->mmtForm);
}
sub my_render{
    my $s = shift;
    my $render = shift;
    $s->stash->{_title} = join('',$s->param('_table'));
    $s->render($render);
}
sub registry{
   my $self = shift;

   $self->action_set();
   my $log = Mojo::Log->new();
   $log->debug( "IN registry" );

   for my $action  (@{$self->{'_action'}}) {
       if($action->{'name'} eq $self->param('_action')){
           return $action->{action}();
       }
   }
   $self->stash->{'_dumper'} = $self->dumper ($self->param()) .
       $self->param('_action');
   $self->render('mmt/dumper');
}
 .
 .
 .

templates/mmt/mainform.html.ep # データメンテナンス画面

  • $selfでコントローラのメソッドが使える
% layout 'default';
% title "mmt - $_title " ;
<h1><%= $_title %></h1>

%= form_for url_for('_table'=> param('_table')) => (method => 'POST') => begin
<%= hidden_field timestamp => param('timestamp') %>
<%= hidden_field _table => param('_table') %>
<table>
% for my $item (@{$self->{'m'}->{'key'}}){
 <tr>
  <th><div id="<%= 'l_' . $self->{'n'}->{$item} %>"> <%= $self->Label($item) %></div></th>
  <td><%== $self->input_field($item) %>
      <%== $self->get_explan(param('_table'),$item) %></td>
  <td><div id="<%= 'd_' . $self->{'n'}->{$item} %>">
 </tr>
% }
</table>
%= submit_button $self->{'_action'}[0]->{'name'} ,id => '_action',name => '_action'
<%== $self->serch_input_field() %>
%= submit_button $self->{'_action'}[4]->{'name'} ,id => '_action',name => '_action'
%= submit_button $self->{'_action'}[6]->{'name'} ,id => '_action',name => '_action'
<br>
INFO:<%= $self->{errstr} %>
<hr>
<table>
% my $i = 0;
% for my $item (@{$self->{'m'}->{'item'}}){
 <tr>
  <th><div id="<%= 'l_' . $self->{'n'}->{$item} %>"> <%= $self->Label($item) %></div></th>
  <td><%== $self->input_field($item) %>
      <%== $self->get_explan(param('_table'),$item) %></td>
  <td><div id="<%= 'd_' . $self->{'n'}->{$item} %>"></div></td>
 </tr>
% }
</table>

% for my $item (@{$self->{'_action'}}[1..3]){
%= submit_button $item->{'name'} ,id => '_action',name => '_action'
%  }
% end
<hr>
<form method="post" action="<%= url_for('_table'=> param('_table')) %>"
            enctype ="multipart/form-data">

        <input type="file" name="upload_file" />
        <%= hidden_field _table => param('_table') %>
        <input type="submit" value="Upload" name="_action" />
</form>

Mojolicious::Controllerのrenderメソッドを呼び出すことで、レンダラを手動で起動できます。しかし、通常は必要ありません。なぜならルータの処理が終わったとき、何もレンダリングされていない場合はレンダラが自動的に呼び出されるからです。これは、何もアクションを実行しない、テンプレートだけを指し示すルーティングを作成できるということです。

$c->render;

ただし、大きな違いがひとつあります。renderを手動で呼ぶことによって、テンプレートが現在のコントローラオブジェクトを使用し、アプリケーションクラスのMojoliciousのcontroller_class属性で指定されたデフォルトコントローラを使用しないことを保証できます。

templateからcontrollerのmethodを実行する為にcontrollerの最後にrenderを追加

$ git diff  eafe8b7b1b21773dcf4726a0c7ee1a944eb55be5
diff --git a/toolmmt/lib/Tool/mmt/Controller/Json.pm 
b/toolmmt/lib/Tool/mmt/Controller/Json.pm
index ba51f87..ab00b7a 100644
--- a/toolmmt/lib/Tool/mmt/Controller/Json.pm
+++ b/toolmmt/lib/Tool/mmt/Controller/Json.pm
@@ -27,8 +27,13 @@

  sub json{
      my $s = shift;
-     $s->render(json => $s->req->json) if($s->req->json);
+     if($s->req->json){
+        $s->render(json => $s->req->json);
+     }else{
+        $s->render('json/json');
+     }
  }
+
  sub json_or_jsonp{
      my $s = shift;
      my $json = shift;
diff --git a/toolmmt/lib/Tool/mmt/Controller/Menu.pm 
b/toolmmt/lib/Tool/mmt/Controller/Menu.pm
index 69da8cb..ef1ca1f 100644
--- a/toolmmt/lib/Tool/mmt/Controller/Menu.pm
+++ b/toolmmt/lib/Tool/mmt/Controller/Menu.pm
@@ -7,6 +7,7 @@
      my $s = shift;
      my $data = $s->menu_get();
      $s->stash(_data=> $data);
+     $s->render('menu/menu');
  }
  sub menu_get{
      my $s = shift;

templates/mmt/datalist.html.ep # 一覧表示画面

% layout 'default';
% title "mmt - $_title";
<h1><%= $_title %></h1>
<table><tr><th></th>
% for my $item (@{$self->{'m'}->{'key'}},@{$self->{'m'}->{'item'}}){
  <th><div id="<%= $item %>"> <%= $self->Label($item) %></div></th>
% }
</tr>
% my $count = 0;
% while (my $ref = $self->{'sth'}->fetchrow_hashref()){
%   $count++;
%   last if $count > 2000;
<tr>
%= form_for url_for('_table'=> param('_table')) => (method => 'POST') => begin
<%= hidden_field _table => param('_table') %>
    <td>
    %= submit_button $self->{'_action'}[0]->{'name'} ,id => '_action',name => '_action'
    </td>
%   for my $name (@{$self->{'m'}->{key}}){
        <td><%= $ref->{Encode::encode("utf8",$name)} %>
        <%= hidden_field $self->{'n'}->{$name} => $ref->{Encode::encode("utf8",$name)} %>
        </td>
%   }
%   for my $name (@{$self->{'m'}->{item}}){
        <td><%= $ref->{Encode::encode("utf8",$name)} %></td>
%   }
% end
</tr>
% }
</table>
%=   "* No Data *" if ($count == 0);
%=   "* Max over * " if ($count > 2000);

templates/mmt/Commodity.pm # Mmtを継承したアプリケーション

package Tool::mmt::Commodity;
use Mojo::Base 'Tool::mmt::Mmt';

has 'mmtDataList' => 'mmt/datalist';

sub init_set {
    my $s = shift;
    $s->mmtForm('mmt/mainform');
    $s->param('_table','商品');
}
sub look_up_set{
    my $s = shift;
    $s->{'m'}->{LOOK_UP}->{ref $s}->{$s->{'n'}->{'大分類'}} = 
        ["select 略称 from 分類名称 where 中分類 = '' and 小分類 = '' and 大分類 = ? ", 
            [$s->{'n'}->{'大分類'}] ];
    $s->{'m'}->{SUBWIN}->{ref $s}->{$s->{'n'}->{'大分類'}} = 
        ["select 大分類,略称 from 分類名称 where 中分類 = '' and 小分類 = ''", 
             []];

    $s->{'m'}->{LOOK_UP}->{ref $s}->{$s->{'n'}->{'中分類'}} = 
        ["select 略称 from 分類名称 where 大分類 = ? and 中分類 = ? and 小分類 = '' ", 
            [$s->{'n'}->{'大分類'},$s->{'n'}->{'中分類'}] ];
    $s->{'m'}->{SUBWIN}->{ref $s}->{$s->{'n'}->{'中分類'}} = 
        ["select 中分類,略称 from 分類名称 where 大分類 = ? and 中分類 <> '' and 小分類 = ''", 
            [$s->{'n'}->{'大分類'}] ];

    $s->{'m'}->{LOOK_UP}->{ref $s}->{$s->{'n'}->{'小分類'}} = 
        ["select 略称 from 分類名称 where 大分類 = ? and 中分類 = ? and 小分類 = ? ", 
            [$s->{'n'}->{'大分類'},$s->{'n'}->{'中分類'},$s->{'n'}->{'小分類'}] ];
    $s->{'m'}->{SUBWIN}->{ref $s}->{$s->{'n'}->{'小分類'}} = 
        ["select 小分類,略称 from 分類名称 where 大分類 = ? and 中分類 = ? and 小分類 <> ''", 
            [$s->{'n'}->{'大分類'},$s->{'n'}->{'中分類'}] ];
}

1;

TODO

  • validation
  • 検索SUB画面 (1/7)
  • 綺麗な画面
  • ENTERで項目移動 (12/9)
  • セッション管理 (2019/11/19)
  • login処理 (2019/11/19)
  • メニュー作成 (2020/01) メニュー
  • レポートライターツール (2017/04/09)

ENTERで項目移動 (12/9)

[toolmmt/templates/layouts/toolmmt/templates/layouts]
 <!DOCTYPE html>
 <html>
   <head><title><%= title %></title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
      <script type="text/javascript">  <!-- [jQuery] Enterキーでフォーカスを移動するには http://blog.makotoishida.com/2013/02/javascript-enter.html -->
             $(function(){
               var elements = "input[type=text]";
               $(elements).keypress(function(e) {
                 var c = e.which ? e.which : e.keyCode;
                 if (c == 13) { 
                   var index = $(elements).index(this);
                   var criteria = e.shiftKey ? ":lt(" + index + "):last" : ":gt(" + index + "):first";
                   $(elements + criteria).focus();
                   e.preventDefault();
                 }
               });
             });
      </script>
   </head>
   <body><%= content %></body>
 </html>

ajaxにてテーブル参照とサブウインドを開く

 sub make_ajax{
     my $s = shift;
     my $p = '';
     my $onload = '';
     #
     # マスタ参照ajax(LOOK_UPをサーチ)
     #
     for (keys %{$s->{'m'}->{LOOK_UP}->{ref $s}}){
         next unless ($_ =~ /^(\D)+(\d)+$/);
         $p .= $s->new_updater($_);
         $onload .= "\$('#$_').change();\n";
     }
     #
     # マスタ参照ウインド(WUBWINをサーチ)
     #
     for (keys %{$s->{'m'}->{SUBWIN}->{ref $s}}){
         next unless ($_ =~ /^(\D)+(\d)+$/);
         $p .= $s->new_subwin($_);
     }
     return $p . $onload;
 }
 #
 # マスタ参照(http://www21051ue.sakura.ne.jp:3003/mmtx/commodity?_action=get_name&n=item5&p=001&p=002)
 #
 sub get_name{
     my $s = shift;
     my @names = qw/未登録/;
     #eval {
         @names = $s->app->model->webdb->dbh->selectrow_array(
                     $s->{'m'}->{LOOK_UP}->{ref $s}->{$s->param('n')}->[0],undef,$s->param('p'));
     #};
     if ($@){
         $s->render(json=>$@);
     } else{
         my $json = {rec=>@names};
         $s->render(json=>$json);        # JSONを描画する
     }
 }
 sub subwin{
     my $s = shift;
     my $subwin = $s->{'m'}->{SUBWIN}->{ref $s}->{$s->param('n')};
     my $sql = $subwin->[0];
     my $render = $subwin->[2] || 'mmt/subwin';
     my $param = $s->param('p');
     my $dbh = $s->app->model->webdb->dbh;
     $s->{'sth'} = $dbh->prepare($sql);
     $s->{'sth'}->execute($s->param('p'));
     $s->stash->{_title} = '検索';
     $s->stash->{_sql} = $sql;
     $s->stash->{_controller} = ref $s;
     $s->render($render);
 }
 sub new_updater{
     my $s = shift;
     my $n = shift;
     my $p = '"';
     $p .= join '',map{qq{ + "&p=" + \$('#$_').val()}} @{$s->{'m'}->{'LOOK_UP'}->{ref $s}->{$n}->[1]};
     return <<End_Script;
 jQuery('#$n').change( function (){                  // 内容が変化した時に実行
     jQuery.ajax({                                   // http通信を行う
      type: 'GET',                                   // 通信種類を指定(GET,POST,PUT,DELETE)
      dataType: 'json',                              // サーバーから返されるデータタイプ
      data: "_action=get_name&n=$n$p ,               // サーバーに送信する値
      success:function(data,textStatus,jqXHR){       // ajax通信が成功した時のajax event
       jQuery('#d_$n').html(data.rec);               // 返値を描画
      return false;
      }
     });
 });
 End_Script
 }
 sub new_subwin{
     my $s = shift;
     my $n = shift;
     my $p = '"';
     $p .= join '',map{qq{ + "&p=" +  \$('#$_').val()}} @{$s->{'m'}->{'SUBWIN'}->{ref $s}->{$n}->[1]};
     my $win_para = "width=600,height=500,resizable=yes,scrollbars=yes";
     return <<End_Script;
 jQuery(document).ready(function(){                  
     jQuery('#l_$n').html(                           // LABELをボタンに変更する
         "<input type=button value=@{[$s->Label($s->get_input_name($n))]}>");
 });
 
 jQuery('#l_$n').click( function (){                 // SUB WINDOWを開く
     window.open("@{[$s->url_for->query(            
                         _action=>'subwin'
                         ,n=>$n
                         )
                   ]}$p,'_blank','$win_para');
     return false;
 });
 End_Script
 }
 
 1;

SUB WINDOW

[layouts/defsubwin.html.ep]
<!DOCTYPE html>
<html>
  <head><title><%= title %></title>
   <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
     <script type="text/javascript">  <!-- [jQuery] Enterキーでフォーカスを移動するには http://blog.makotoishida.com/2013/02/javascript-enter.html -->
            $(function(){
              var elements = "input[type=text]";
              $(elements).keypress(function(e) {
                var c = e.which ? e.which : e.keyCode;
                if (c == 13) { 
                  var index = $(elements).index(this);
                  var criteria = e.shiftKey ? ":lt(" + index + "):last" : ":gt(" + index + "):first";
                  $(elements + criteria).focus();
                  e.preventDefault();
                }
              });
            });
            function setVal(terget,val){
                window.opener.$("#"+terget).val(val);           // 選択値を親画面にセットする
                window.opener.$("#"+terget).change();           // changeイベントを発生する
                window.opener.$("#"+terget).focus();            // フォーカスを移動する
                window.close();                                 // 自ウィンドを閉じる
            }
     </script>
     <%= stylesheet '/css/default.css' %>
  </head>
  <body>
    <div class="subwin">
      <%= content %>
    </div>
  </body>
</html>
[mmt/subwin.html.ep]
% layout 'defsubwin';
% title "mmt - $_title";
<h1><%= $_title %></h1>
<%= $_sql %><br />
[<%= $_controller %>][<%= param('n') %>][<%= join '|',param('p') %>]
<table border=1><thead><tr>
% for my $item (@{$self->{'sth'}->{'NAME'}}){
  <th><div id="<%= $item %>"> <%= Encode::decode('UTF-8',$item) %></div></th>
% }
</tr>
</thead>
<tbody>
% my $count = 0;
% while (my $ref = $self->{'sth'}->fetchrow_arrayref()){
%   $count++;
%   last if $count > 2000;
<tr>
%   my $i = 0;
%   for my $item (@{$ref}){
%       $i++;
        <td>
%       if($i == 1){
            <a href="" onclick="setVal('<%= param('n') %>','<%= $item %>');">
%       }
        <%= $item %></td>
%   }
</tr>
% }
</tbody>
</table>
%=   "* No Data *" if ($count == 0);
%=   "* Max over * " if ($count > 2000);