Title: プロジェクトやマイルストーンのタブを拡張(追加)するには

Forum1220 を利用して、プロジェクトやマイルストーンのタブを拡張(追加)できます。

ここでは、「ダッシュボード」タブの次に(右側に)「FAQ」というタブを追加する例で解説します。



プラグインのフォルダ



「ダッシュボード」タブの次に(右側に)「FAQ」というタブを追加する、という動作を行う「Project FAQ Tab」プラグインを作成します。

TeamPage がインストールされた server ディレクトリ(フォルダ)の下の plugins フォルダに「com.traction.goalfaqtab」という名前のフォルダを作成します。

プラグインのフォルダ

メモ: プラグイン用のフォルダの名前は、通常は「プラグイン名.作成者のドメイン名」を逆にしたものになります。例えば「ABC」という名前のプラグインを「traction.co.jp」が作成する場合、フォルダ名は「jp.co.traction.abc」になります。しかし、必ずしもこのルールに従う必要はありません。 (参照: FAQ1683.08: TeamPage プラグイン概要



メモ: TeamPage の開発の歴史的な理由により、「プロジェクト」は、SDL プログラムの世界では「goal」と呼ばれています。そこで、今回、プラグインのフォルダを「com.traction.projectfaqtab」ではなく「com.traction.goalfaqtab」とします。



プラグインの定義ファイル



作成した「com.traction.goalfaqtab」フォルダの中に plugin.properties ファイルを作成し、テキストエディタで開き、次のように記述して保存します。

display_name=Project FAQ Tab
description=Inserts the "FAQ" tab next to the Dashboard tab.
name=com.traction.goalfaqtab
version=1.0


サーバーセットアップ >一般 > サーバー管理 ページで [キャッシュのクリア] ボタンをクリックし、サーバーセットアップ >プラグイン ページを開きます。インストール済みプラグイン一覧に「Project FAQ Tab」が表示されたことを確認します。



新しいタブを追加するには



プロジェクトのタブはどこで定義されている?



プラグインには「ダッシュボード」「タスク」「マイルストーン」などのタブがありますが、これらはどこで定義されているのでしょうか?



その答えは、server ディレクトリの下の、src フォルダの下の、com フォルダの下の、traction フォルダの下の、sdl フォルダの下の、gwtrpc フォルダの中にある、goal.sdl ファイルの中の「bodytabs」関数(sdl.function)です。

テキストエディタで goal.sdl を開き、次のようにタブの名前(タブのテキスト)やリンク先 URL が JSON 形式で定義されていることを確認してください。

<sdl.function name="bodytabs">
  <gwt.rpc.view name="BodyTabs" key="heading">
    { tabs: [
    { t: "#{gwtrpc_tab_dashboard}", l: "<shared#addentry href='#/project/dashboard' />"<compare.equals "<config.view.value name='viewtype' />" "single" "goal_dashboard">, x: true</compare.equals> },
    { t: "#{gwtrpc_subtab_tasks_overview}", l: "<shared#addentry href='#/project/tasks' />" },
    { t: "#{gwtrpc_subtab_tasks_milestones}", l: "<shared#addentry href='#/project/milestones' />" },
    { t: "#{gwtrpc_tab_activity}", l: "<shared#addentry href='#/project/activity' />" },
    { t: "#{gwtrpc_tab_status}", l: "<shared#addentry href='#/project/status' />" },
    { t: "#{gwtrpc_tab_calendar}", l: "<shared#addentry href='#/project/cal' />&edate=month" },
    { t: "#{gwtrpc_tab_documents}", l: "<shared#addentry href='#/project/docs' />"<scope.hasfile>, x: true</scope.hasfile> },
    { t: "#{gwtrpc_subtab_goal_discussion}", l: "<shared#addentry href='#/project/discussion' />" },
    { t: "#{related_entries}", l: "<shared#addentry href='#/project/related' />" },
    { t: "#{entrytabs_history}", l: "<shared#addentry href='#/history' />" }
    ] }
  </gwt.rpc.view>
</sdl.function>


「ダッシュボード」タブの次に(右側に)「FAQ」タブを挿入するには、この JSON を変更すれば良いことになります。

複数のプラグインによる衝突を避けるために



しかし、もし「『FAQ』タブを追加するプラグイン」の他に「『Wiki』タブを追加するプラグイン」があったらどうなるでしょうか。

前者が変更した「bodytabs」関数を後者の「bodytabs」関数が上書きするため、前者の変更が無効になってしまいます。

メモ: 上書きされる「前者」と上書きする「後者」は、インストール済みプラグイン一覧に表示されるプラグインの順序で決まります。



そこで Forum1220 では、この「bodytabs」関数を次のように記述し、タブ毎に「子ども」関数をロードするように変更しました。

 <sdl.function name="bodytabs-inner">
  <foreach list="<config.project.value name='custompmentrytabs_goal_tabs' />">
    <switch value="__foreach.current__">
      <case value="dashboard">
        <#bodytabs-dashboard />
      </case>
      <case value="tasks" value="task" value="tasks_overview">
        <#bodytabs-tasks />
      </case>
      <case value="milestones" value="milestone">
        <#bodytabs-milestones />
      </case>
      <case value="activity" value="activities">
        <#bodytabs-activity />
      </case>
      <case value="status">
        <#bodytabs-status />
      </case>
      <case value="calencar" value="cal">
        <#bodytabs-calendar />
      </case>
      <case value="documents" value="sharefolder">
        <#bodytabs-documents />
      </case>
      <case value="comments" value="discussion">
        <#bodytabs-discussion />
      </case>
      <case value="related">
        <#bodytabs-related />
      </case>
      <case value="history">
        <#bodytabs-history />
      </case>
    </switch>
  </foreach>
</sdl.function>


例えば「タスク」タブを表示するための「子ども」関数である「bodytabs-tasks」は次のように記述されています。

<sdl.function name="bodytabs-tasks">
  { t: "#{gwtrpc_subtab_tasks_overview}", l: "<shared#addentry href='#/project/tasks' />" },
</sdl.function>


この関数を実行すると(呼び出すと)、「タスク」タブの JSON が単純に返されます。

{ t: "#{gwtrpc_subtab_tasks_overview}", l: "<shared#addentry href='#/project/tasks' />" },
 


「FAQ」タブの作成と配置



「FAQ」タブの関数の作成



まず、「FAQ」タブの JSON を返す新しい関数「bodytabs-faq」を、「com.traction.goalfaqtab」の中の goal.sdl に定義します。

「com.traction.goalfaqtab」フォルダの下に、com/traction/sdl/gwtrpc のフォルダ階層を作成し、その中に goal.sdl ファイルを作成します。



テキストエディタで開き、次のように「bodytabs-faq」関数を次のように記述します。これは「『FAQ』というタブを作成し、クリック時に "#/project/faq" という URL へ移動する」という意味です。

<sdl.function name="bodytabs-faq">
  { t: "FAQ", l: "<shared#addentry href='#/project/faq' />" },
</sdl.function>


「ダッシュボード」タブの隣に配置



上記の「FAQ」タブを「ダッシュボード」タブの次に表示するには、「bodytabs-dashboard」の JSON の次に「bodytabs-faq」の JSON がロードされるようにする必要があります。

そこで、「bodytabs-dashboard」を次のように記述します。(上記の「bodytabs-faq」関数の上下どちらかに追記します)

<sdl.function name="bodytabs-dashboard">
  <##bodytabs-dashboard />
  <#bodytabs-faq />
</sdl.function>


2 行目は、「この『bodytabs-dashboard』関数自身が実行される前の『bodytabs-dashboard』を呼び出せ」という意味です。二重シャープ記号で関数名を指定することで、「自分自身と同じ名前の関数の、自分自身が実行される前の状態」を呼び出すことができます。

つまり、「com.traction.goalfaqtab」プラグインのこの「bodytabs-dashboard」関数が上書きする前の、「com.traction.custompmentrytabs」プラグインの「bodytabs-dashborad」関数(で定義されている、「ダッシュボード」タブ用の JSON)を呼び出しています。

3 行目は、二重シャープ記号ではなく一重シャープ記号ですので(「一重」という日本語はおかしいですが)、関数「bodytabs-faq」を指定して呼び出しているだけです。

よって、この「bodytabs-dashboard」を実行すると、2行目と3行目を合わせて次の JSON コードが返されます。(実際には <shared#addentry href='URL' /> の部分が share.sdl の「addentry」関数からの返り値として挿入されます)

{ t: "#{gwtrpc_tab_dashboard}", l: "<shared#addentry href='#/project/dashboard' />", x: true },
{ t: "FAQ", l: "<shared#addentry href='#/project/faq' />" },


「FAQ」タブの表示確認



サーバーセットアップ >一般 > サーバー管理 ページで [キャッシュのクリア] ボタンをクリックし、プロジェクト記事を表示します。下図のように「ダッシュボード」タブの右側に「FAQ」タブが表示されれば成功です。



しかし、この「FAQ」タブをクリックすると、下図のように「tsi.xcp.NoMatchingViewException: 該当するビューはありません」エラーになります。



「FAQ」タブをクリックしたとき、"#/project/faq" という URL へ移動しようとしますが、その移動先のビューが存在しないためです。「プロジェクト記事の中のFAQ」という画面が定義されていないため、TeamPage はどんな内容を表示すれば良いのかわからないのです。

そこで、、"#/project/faq" という URL に対応するビューを作成します。

対応するビューを作成するには



ビューの定義



ビューの定義ファイルは、config/gwt/views に置かれた .properties ファイルです。URL が "#/project/faq" のビューの定義ファイルは、project_faq.properties という名前になります。

プラグインのフォルダ内に config/gwt/views という階層を作り、その中に project_faq.properties という名前のファイルを作成します。



テキストエディタで開いて次のように記述して保存します。

# 「project_tasks.properties」の設定を引き継ぐ
__inherits=project_tasks
# 「viewtype」という属性の値を「goal_faq」とする
viewtype=goal_faq


ビューを表示する SDL テンプレート



テンプレートの所在



上記のように、この「project_faq」のビューは「project_tasks」の設定を引き継いでおり、さらに「project_tasks」は「project_base」を引き継いでいます。(server/config/gwt/views フォルダ内の .properties ファイルで確認できます)

project_base.properties ファイルでは、 sdl= 行で、次のように画面表示に使う .sdl ファイルが指定されています。

sdl=com.traction.sdl.gwtrpc.goal


server/src/com/traction/sdl/gwtrpc/goal.sdl をテキストエディタで開き、キーワード「project_tasks」で検索して「entry」関数を見つけます。この「entry」関数が、プロジェクト記事のシングルビューを表示(レンダリング)するテンプレートになっています。

この「entry」関数の中にある <switch>〜</switch> の部分(下記)を確認します。

<switch value="<config.view.value name='viewtype' />">
  <case value="goal_dashboard" value="single">
    <#dashboard />
  </case>
  <case value="goal_tasks">
    <#tasks />
  </case>
  <case value="goal_milestones">
    <#milestones />
  </case>
  <case value="goal_activity">
    <com.traction.sdl.tasks.actfeed#entry-activity />
  </case>
  <!--- This view configuration still exists, so we still handle it,
  but isn't exposed in the UI. [shep 28.Mar.2011] -->
  <case value="goal_desc">
    <shared#entry-desc />
  </case>
  <case value="goal_cal">
    <#calendar />
  </case>
  <case value="goal_docs">
    <shared#entry-docs />
  </case>
  <case value="goal_discussion">
    <shared#entry-discussion />
  </case>
  <case value="goal_related">
    <shared#entry-related />
  </case>
  <case value="goal_status">
    <shared#entry-status />
  </case>
</switch>


<switch> タグは、value 属性の値に対応した <case> タグに条件分岐を行います。

<config.view.value name='viewtype' /> は、ビューの定義ファイル(config/gwt/views の中の .properties ファイル)で定義された「viewtype」という名前の属性の値を得るタグです。「project_faq」ビューを表示するとき、ビューの定義ファイルである config/gwt/views/project_faq.properties ファイルの中の viewtype= 行の右辺である「goal_faq」という値になります。

つまり、この <switch>〜</switch> 内に「goal_faq」の条件分岐のための <case value="goal_faq">〜</case> を挿入すれば良いわけです。

「entry」関数を丸ごとコピーして利用する



それでは、既存の src/com/traction/sdl/gwtrpc/goal.sdl の「entry」関数を丸ごとコピーし、プラグインのフォルダの中の com/traction/sdl/gwtrpc/goal.sdl に貼り付け(追加し)ます。

そして、<switch>〜</switch> 部分を次のように書き換えます。(2行目から4行目に、「viewtype の値が "goal_faq" だったら」の分岐(<case>〜</case>)を挿入します。)

<switch value="<config.view.value name='viewtype' />">
  <case value="goal_faq">
    <#faq />
  </case>
  <case value="goal_dashboard" value="single">
    <#dashboard />
  </case>
  <case value="goal_tasks">
    <#tasks />
  </case>
  <case value="goal_milestones">
    <#milestones />
  </case>
  <case value="goal_activity">
    <com.traction.sdl.tasks.actfeed#entry-activity />
  </case>
  <!--- This view configuration still exists, so we still handle it,
  but isn't exposed in the UI. [shep 28.Mar.2011] -->
  <case value="goal_desc">
    <shared#entry-desc />
  </case>
  <case value="goal_cal">
    <#calendar />
  </case>
  <case value="goal_docs">
    <shared#entry-docs />
  </case>
  <case value="goal_discussion">
    <shared#entry-discussion />
  </case>
  <case value="goal_related">
    <shared#entry-related />
  </case>
  <case value="goal_status">
    <shared#entry-status />
  </case>
</switch>


「FAQ記事を一覧表示する」部分を作成する



この <case>〜</case> 内の <#faq /> は、「『faq』という名前の関数を呼び出せ」という意味です。それでは、その「faq」関数を作成します。

ここでは「プロジェクトに関連付けられており、なおかつ『faq』というタグが付いている記事」を一覧表示する SDL プログラムを組みます。(プロジェクトの「FAQ」タブをクリックしたら、プロジェクトと関連のある「FAQ」タグの付いた記事が一覧表示されるようにします。)

記事の一覧を得るには、<entries>〜</entries> という SDL タグを使います。パラメーターを使って記事一覧の範囲を限定できます。ここでは type= パラメーターで cat (タグが付いた記事)を、cat= パラメーターでプロジェクトとの関連性タグ(Entry Label)を、search= パラメーターで「FAQ」タグを指定することで、「プロジェクトに関連付けられており、なおかつ『faq』というタグが付いている記事」を取得します。

<sdl.function name="faq">
  <gwt.rpc.view name="HTML" region="entries"> 
    <#faq-entries />
  </gwt.rpc.view>
  <shared#chunkmore-custom />
</sdl.function>

<sdl.function name="faq-entries">
  <div class="entries faq-entries">
    <div class="feed">
      <entries type="cat" cat=":-e:goal:__entry.tractionid.entry__" search='x(::*:FAQ)' proj="*" volume="feed">
        __entry.content__
      </entries>
    </div>
  </div>
</sdl.function>


動作確認



サーバーセットアップ >一般 > サーバー管理 ページで [キャッシュのクリア] ボタンをクリックし、プロジェクト記事の「FAQ」タブを再度開きます。下図のように、プロジェクトに関連付けられていて、なおかつ「FAQ」タグが付いた記事が一覧表示されれば成功です。



さらに…



「次のN件を表示」の実装



以上で、プロジェクトに新しい「FAQ」タブを追加し、その「FAQ」タブがクリックされたときのビュー「project_faq」を作成することができました。

ただ、この「project_faq」ビューには、「FAQ」タグが付いた記事がすべて表示されます。例えば、10,000 件の「FAQ」タグ付き記事があると、その 10,000 件がすべて表示されます。

アクティビティ ビューなどでは、標準で 25 件ずつの記事が投稿され、画面下部へスクロールすると次の 25 件が表示される…という、チャンク機能が実装されています。このチャンク機能を「project_faq」に実装するには、ビューの定義ファイルと SDL のプログラム コードの両方での対応が必要です。

この方法を解説するとまた長くなるので(もう既に長いですが…)別の機会に解説します。

メモ: この「25件ずつ」の件数は、ユーザー毎に、パーソナルセットアップの 1画面に表示する記事の数 で変更できます。または、サーバーセットアップ > 既定値で、すべてのユーザーの既定の件数を変更できます。



プラグインのダウンロード



下のリンクをクリックして、この解説で作成したプラグイン「com.traction.goalfaqtab」をダウンロードできます。解答/展開してファイルの階層やプログラム コードの確認にご利用ください。

com.traction.goalfaqtab-1.0.zip



Attachments:
new_plugin_dir.png
standard_project_tabs.png
plugin_enabled.png
goal_faq_tab_showup.png
new_goal_sdl.png
error.png
views_properties_dir.png
faq_entries.png
com.traction.goalfaqtab-1.0.zip
関連記事
Article: DocSDK103 (permalink)
Categories: :FAQ:カスタマイズ, :FAQ:スキン:Proteus, :DocSDK:FAQ, :DocSDK:プラグイン
Date: 2014/10/14; 16時57分58秒 JST

Author Name: TeamPage サポート
Author ID: jpbo