MT5.1のリスティングフレームワークでちょっとした問題に直面しました。

リスティングフレームワークで画面のタイトルを設定するには、listing_screensのscreen_labelにその値を設定します。しかしこのscreen_labelの値に「[」「]」が含まれていると、エラーとなる現象が発生しました。

例えば "abc[test]xyz" のような値はエラーとなります。

これは、screen_labelで設定した値が、最終的にMT::Componentにてtranslateされるために起こるようです。
translateする際に「[」「]」といった文字は特殊な働きをするため、不具合を生じてしまうようです。


正攻法とは言えないかもしれないですが、以下のように対処しました。

* まず、listing_screensのscreen_labelは設定しない。
* list_template_paramのコールバックで、page_titleに画面のタイトルを設定(「[」「]」が含まれている)

今回はページタイトルさえ設定できれば良いということで、listing_screensではタイトルを設定せず、テンプレートパラメータ設定のコールバックでページタイトルをpage_titleとして設定してやりました。

コードとしては

$plugin => registry({
  listing_screens => {
    hoge => {
      #ここで設定しない  screen_label => 'abc[test]xyz',
      ・・・(略)・・・
    }
  },
  callbacks => {
    'list_template_param.hoge' => sub {
        my ($cb, $app, $param, $tmpl) = @_;
        $param->{page_title} = 'abx[test]xyz';    # こっちで設定
    },
  },
  ・・・(略)・・・
});

こんな感じの対応です。
今回はMT5.1リスティングフレームワークにコンテンツアクションを実装してみます。

コンテンツアクションとは、例えば ツール>ログ の上部に並んでいる
「ログを消去」「ログをダウンロード(CSV)」といったリンクのことです。

lfw6.jpg




画面のテンプレートに独自テンプレートを使用しているのであれば、
このくらいのリンクは、直接テンプレートに書いてもいいかもしれないです。

今回はMTが用意してくれている汎用的なテンプレート(具体的にはtmpl/cms/list_common.tmpl)の使用を前提に進めているので、勝手に書き加えるわけにはいきません。
(テンプレートを指定しなければ、このlist_common.tmplが使用されるようです)


コンテンツアクションを実装するには、applications => cms => contents_actions を記述します。

list_properties => {
  mytest => {
     (略)
  }
},
applications => {
  cms => {
    contents_actions => {
      mytest => {
          download_hoge => {
             class => 'icon-download',
             label => 'Backup hoge(CSV)',
             mode => 'download_hoge',
             args => {
                foo => 'abc',
             },
             order => 100,
          },
          reset_hoge => {
             class => 'icon-action',
             label => 'Reset hoge',
             mode => 'reset_hoge',
             args => {
                foo => 'abc',
             },
             order => 200,
             confirm_msg => 'Reset Hoge data. Are you sure?',
          },
       },
    },
  },
},

「Backup hoge(CSV)」「Reset Hoge」の二つのアクションを追加しました。


lfw5.jpg

それぞれクリックすると、指定したmodeが実行されます。
download_hogeであれば、 ?__mode=download_hoge&foo=abc
reset_hogeであれば、 ?__mode=reset_hoge&foo=abc が実行されます。

2つともほぼ同じ内容ですが、reset_hogeにはconfirm_msgを設定してあります。
この場合、クリックした際にjsでconfirmが表示されます。

lfw7.jpg

今回はリスティングフレームワークに削除機能を実装してみます。
さっそくコード、

        'list_actions' => {
            mytest => {
                'delete' => {
                   label      => 'Delete',
                   code       => "Common::delete",
                   mode       => 'delete',
                   order      => 100,
                   js_message => 'delete',
                   button     => 1,
                },
            },
        },

これだけで実装できました。

lfw3.jpg

ソートやフィルタ機能はlist_propertiesに記述してきましたが、削除機能はlist_actionsに記述します。
https://github.com/movabletype/Documentation/wiki/Ja-dev-registry-list-actions
list_actions、まだドキュメントが空っぽなんですよね。上のソースはMTのソースを真似て書いたものです。

このlist_actionsに記述したものが、リスト左上にボタンやドロップダウンとして並ぶようです。
list_actionsが空でなければ、自動で各行にチェックボックスが表示されるようです。

特殊な処理が必要なければ、上のコードだけでMTが削除処理の面倒をみてくれます。
チェックボックスでデータを選択して削除ボタンを押すと、
jsで削除の確認が出て、OKを押せばチェックしたデータが削除されます。
この一連の処理を全部MTがやってくれます。すごく楽ですね。

ちなみにbutton => 0 にすると、UIがボタンではなくドロップダウンになります。

lfw4.jpg
前回は独自の列にソート機能を実装しました。
今回はフィルタリング機能を実装してみます。

リスティングフレームワークには、いくつかのフィルタタイプが用意されています。

  • 文字列タイプ
  • 数値タイプ
  • 日付タイプ
  • リスト選択タイプ

など、タイプによってフィルタのUIが異なります。
今回は独自の列である「Hoge」列に、「文字列タイプ」のフィルタを実装することにします。
必要になるのは filter_tmpl と grep です。

        list_properties => {
          mytest => {
            id => {
              auto => 1,
              label => 'ID',
              order => 100,
            },
            hoge => {
              label => 'Hoge',
              order => 200,
              raw => sub {
                my $props = shift;
                my ($obj, $app, $opts) = @_;
                return 'hoge' . $obj->id;
              },
              bulk_sort => sub {
                 my $prop = shift;
                 my ($objs, $opts) = @_;
                 return sort { $prop->raw($prop, $a) cmp $prop->raw($prop, $b) } @$objs;
              },
              filter_tmpl => '<mt:var name="filter_form_string">',
              grep => sub {
                my $prop = shift;
                my ($args, $objs, $opts) = @_;

                my $string = $args->{string};
                if ($args->{option} eq 'contains') {
                  return grep { $prop->{raw}($prop, $_) =~ m/$string/ } @$objs;
                }
                elsif ($args->{option} eq 'not_contains') {
                  return grep { $prop->{raw}($prop, $_) !~ m/$string/ } @$objs;
                }
                elsif ($args->{option} eq 'equal') {
                  return grep { $prop->{raw}($prop, $_) eq $string } @$objs;
                }
                elsif ($args->{option} eq 'beginning') {
                  return grep { $prop->{raw}($prop, $_) =~ m/^$string/ } @$objs;
                }
                elsif ($args->{option} eq 'end') {
                  return grep { $prop->{raw}($prop, $_) =~ m/$string$/ } @$objs;
                }
                else {
                  return @$objs;
                }
             },
           },
          },
        },

こんな感じになりました。

filter_tmpl で、フィルタのUIを設定します。
上の例で設定している「<mt:var name="filter_form_string">」はMTが用意してくれているもので、
これを設定することで、文字列タイプのUIが実装されます。
ここにオリジナルのテンプレートを設定すれば、オリジナルのUIに出来るかも?(試してません)

次に grep に、実際のフィルタロジックを設定します。
ここには、3つの引数が渡ってきます。

                my ($args, $objs, $opts) = @_;

$argsには、ユーザが入力した内容が入ってくるようです。
$args->{string} に、入力されたフィルタ文字列。
$args->{option} に、フィルタオプション、文字列タイプであれば、「を含む」「で始まる」などの選択肢です。
それぞれ以下のような値が入ってきます。

を含む: contains
を含まない: not contains
である: equal
で始まる: beginning
で終わる: end

$objsには、フィルタ対象となるオブジェクトの配列のリファレンスになっています。

上の例では、$args->{option}の値(「を含む」「で始まる」など)により、処理を振り分けています。
@$objsに対して、それぞれgrep処理を行い、その結果を返しています。
(配列のリファレンスではなく、配列そのものを返します)


実装結果

lfw2.jpg

フィルタの選択リストに「Hoge」が追加されました。リストから「Hoge」を選択すると上のようなUIが現れます。
条件を複数重ねることもできます。条件を入力して「適用」を押すとフィルタリングが行われました。

このように独自の列でもフィルタリング出来ることが分かりました。

ただし、ソートの時と同様、「パフォーマンスの劣化を招く恐れがあります。」とドキュメントに書かれています。
$objsに全オブジェクトがロードされているので、オブジェクト数が多い場合には影響が出てくると思われます。


以下今後予定している記事です。

  • 削除機能の実装
  • 独自アクションの追加
  • ヘッダ部のカスタマイズ
前回リスティングフレームワークに独自の列を出力しました。
今回はこの独自の列にソート機能を実装してみます。

ソート機能を実装するには、list_properties に bulk_sort を記述します。

list_properties => {
    mytest => {
        id => {
            auto => 1,
            label => 'ID',
            order => 100,
        },
        hoge => {
            label => 'Hoge',
            order => 200,
            raw => sub {
                my $prop = shift;
                my ($obj, $app, $opts) = @_;
                return 'hoge' . $obj->id;
            },
            bulk_sort => sub {
               my $prop = shift;
               my ($objs, $opts) = @_;
               return sort { $prop->raw($prop, $a) cmp $prop->raw($prop, $b) } @$objs;
            },
        },
    },
}

bulk_sortにはオブジェクトの配列のリファレンス($objs)が渡されます。
この配列を自力でソートして返してやる形になります。
上の例は、それぞれのrawの値を求め、文字列として大小比較してソートしています。

lfw2.gif

これだけでHoge列にソート機能が実装されました。

bulk_sortの注意点として、
https://github.com/movabletype/Documentation/wiki/Ja-dev-registry-list-properties
には、「ページに含まれないものを含めた全オブジェクトがロードされます。そのため、パフォーマンスの劣化を招く恐れがあります。」と書かれています。
オブジェクト数があまりに多い場合は、影響がありそうです。
リスティングフレームワークはMTオブジェクトを元に一覧表示する仕組みですが、
オブジェクトに存在しない独自のカラムを出力させることも可能なようです。

その前に、オブジェクトに存在しているカラムを出力する例。

MTオブジェクトmytestのカラムidを一覧の列に追加する一番簡単な設定は、

list_properties => {
  mytest => {
    id => {
      auto => 1,
      label => 'ID',
    },
  },
},

こんな感じになります。
auto => 1にすると自動で色々面倒見てくれるようでありがたいです。
これだけでIDによるフィルター機能も、ソート機能も実装されます。楽ちんです。


では本題のmytestには存在しないHogeという列を追加する例。

list_properties => {
    mytest => {
        id => {
            auto => 1,
            label => 'ID',
            order => 100,
        },
        hoge => {
            label => 'Hoge',
            order => 200,
            raw => sub {
                my $prop = shift;
                my ($obj, $app, $opts) = @_;
                return 'hoge' . $obj->id;
            },
        },
    },
}

と言った感じで label と raw さえあれば十分なようです。
orderは指定しなくても動きますが、適当な並び順になってしまいます。


出力結果
lfw.gif

上の例では、「hoge + オブジェクトのID」をHoge列の値として出力しています。
このように、mytestオブジェクトのカラムであろうが何であろうが列として追加できるようです。


ただし、このように独自に出力した列に関しては、
このままでは、フィルター機能もソート機能も実装されません。
その辺はまた今度記事にしてみます。
MT5.1から実装されたリスティングフレームワークは、URLのパラメータ「blog_id」を元に、MT::Objectに対して自動でblog_idによる絞り込みをかけるようになっています。例えば、

?__mode=list&type=entry&blog_id=3

であれば、WHERE mt_entry.blog_id=3 のデータを一覧表示します。

では、プラグインなどで独自に用意したテーブル「mt_hoge」で、カラムblog_idが存在しない場合はどうなるか?
やはり WHERE mt_hoge.blog_id = 3 を検索しようとして、「カラムblog_idは存在しません」というようなエラーになってしまいます。

困ったなぁ・・・と思っていたらちゃんとオプションが用意されていて、listing_screensのscope_mode を「none」に設定することで、blog_idによる検索を行わないようにできました。

参照
https://github.com/movabletype/Documentation/wiki/Ja-dev-registry-listing-screens