知識画面の実装

メニュー

初めに

 知識関連の画面構成やその遷移に関しては、 こちら を参照してください。この章の全体構成に関する説明は、大分類画面の実装を参照してください。


 知識関連の画面は4つあります。初期画面の xhtml ファイルと管理 Bean クラスは、 knowledgeList.xhtml と KnowledgeListBean になります。
 追加専用画面の xhtml ファイルと管理 Bean クラスは、 newKnowledgePage.xhtml と NewKnowledgeBean になります。
 編集専用画面の xhtml ファイルと管理 Bean クラスは、 updateKnowledgePage.xhtml と UpdateKnowledgeBean になります。
 削除専用画面の xhtml ファイルと管理 Bean クラスは、 removeKnowledgePage.xhtml と RemoveKnowledgeBean になります。

初期画面

クラス定義と主な変数

  KnowledgeListBean クラスの定義と主な変数に関するソース(部分)を示します。メソッドは省略してあります。

                     
@Named
@SessionScoped
public class KnowledgeListBean extends PagingModel implements Serializable {
      
    private String majorId = "";
    
    private String minorId = "";
    
    private String title = "";
    
    private String keyword = "";

    private String selectedId = "";
    
    @Inject
    private MajorDao majorDao;
    
    private List<Major> majors;
    
    @Inject
    private MinorDao minorDao;
    
    private List<Minor> minors;
    
    @Inject
    private KnowledgeDao knowledgeDao;
    
    private List<KnowledgeView> knowledges;
}                         

                 

  majorId、minorId、title、 keyword はそれぞれ画面上の大分類コード、小分類コード、タイトル、キーワード用です。知識の一覧から任意の行をクリックするとその行の表示色を変更しています。その色変更処理と選択行の知識コードを保存する処理は jQuery で行っています。もちろん選択行の保存は、 hidden 属性を付けた input タグを使用しています。そしてその値は selectedId を介して操作しています。

  majorDao は大分類テーブルを表現したインスタンス、 minorDao は小分類テーブルを表現したインスタンス、knowledgeDao は知識テーブルを表現したインスタンスです。それぞれ大分類と小分類と知識の検索・追加・変更・削除というデータベースに関連した操作を行います。 @Inject アノテーションは、このインスタンスの生成は Web サーバに任せていることを示します。これが可能となるのは、 各テーブルクラスに @Stateless アノテーションを付けていることと対応しています。

                     
@Stateless
public class MajorDao {
  …
}


@Stateless
public class MinorDao {
  …
}


@Stateless
public class KnowledgeDao {
  …
}

                 

 ここで今までのクラス MajorPageBean MinorPageBean と違っているのは、親クラスが BaseBean ではなく PagingModel となっていることです。 PagingModel クラスは初めて登場しました。このクラスのソース(部分)を次に示します。

                     
public class PagingModel extends BaseBean {
    …
}

                 

  PagingModel クラスは BaseBean から派生しています。そのため KnowledgeListBean クラスも BaseBean からの派生クラスとなります。そして PagingModel はページングの機能を実現したクラスです。ページング用の画面は、複合コンポーネントである pagingPart.xhtml で定義してあります。つまり knowledgeList.xhtml でページングが必要な部分に pagingPart.xhtml を組み込み、管理 Bean である KnowledgeListBean は PagingModel から派生することで、簡単にページングの機能を実現できるようになっています。このページング用の xhtml とクラスはまた別のところで説明します。

 ページングの機能を直接この画面と管理 Bean で実現することも可能ですが、ページング自体は普通にある機能ですし、多くの画面で利用する可能性が高いです。であるならば、できるだけ汎用性を考慮したやり方で実現したいと考えたため、このような実装方法となりました。

初期化処理

 初期化処理を次に示します。

                    
   @PostConstruct    
    public void initialize()
    {       
        setUrl("knowledgeList.xhtml");
        majors = select(majorDao.findAll());
    }                        

                

 2行目で、全大分類を読みだしています。

 1行目は、ページングの機能に関連しています。ページングは、最初のページ・1つ前のページ・1つ後のページ・最終ページを意味するアイコンをクリックするか、ページ番号のリンクをクリックすることで可能になっています。その時の遷移先の URL を指定しています。ここでは自分自身を指定しています。さらに遷移後のページ番号は GET のパラメータ(デフォルトでは page を使用)するようになっています。この例では、遷移後の完成した URL は、knowledgeList.xhtml?page=xx という書式で展開されます。 xx の部分は数字になります。汎用性を考慮した結果です。

新規の処理

 新規ボタンの xhtml ファイルを示します。(部分) outcome="newKnowledgePage.xhtml" により、別のページに遷移しているだけです。この処理には管理 Bean は関与しません。


    <form jsfc="h:form" >
    …
        <input jsfc="h:button" id="addButton" class="btn btn-primary" type="button" value="新 規" outcome="newKnowledgePage.xhtml" />
    …
    </form>                      

             

検索の処理

 大分類・小分類・タイトル・キーワードの4つを検索条件として、該当する知識を検索することができます。条件を指定しない場合は全件検索となります。 xhtml ファイルの検索ボタンの内容を示します(部分)。


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="readButtonOn" class="btn btn-primary" type="submit" value="検 索" action="#{knowledgeListBean.search()}" />
    …
    </form>                 

                

  KnowledgeListBean の search メソッドが呼び出されます。そのメソッドを示します。

                    
    public void search()
    {
1       KnowledgeCondition cond = new KnowledgeCondition();
2       cond.setMajorId(getMajorId());
3       cond.setMinorId(getMinorId());
4       cond.setTitle(getTitle());
5       cond.setKeyword(getKeyword());
        
6       Long count = (Long) select(knowledgeDao.count(knowledgeDao.makeCountSql(cond)));        
7       setTotalItems(count.intValue());
        
8       String sql = knowledgeDao.makeSql(cond);
9       setSql(sql);
        
10      if (getTotalItems() > 0)
        {
11          knowledges = select(knowledgeDao.findPage(0, getPageItems(), sql));
12          setCurrentIndex(0);
13          setPage(1);
14          setSelectedId("");            
        }
        else
        {
15          knowledges = null;
16          setCurrentIndex(-1);
17          setPage(0);
18          setSelectedId("");            
        }
    }                        

                

 条件検索とページングのために複雑になっています。1行目の KnowledgeCondition は検索条件を表現したクラスです。2~5行で検索条件を設定しています。6行目は、その検索条件に該当するデータの件数を検索しています。ページングを行うには、該当するデータの総数は必須です。7行目で、ページングを担当するクラス PagingModel に対してデータの総数を設定しています。 KnowledgeListBean は PagingModel から派生していますので、 PagingModel の機能がそのまま使用できます。また1ページの表示項目数はデフォルトで10件となっています。
 8行目で、検索に使用する SQL を構築し、9行目で、ページングを担当する PagingModel クラスにその SQL 文を記録しています。ページングの度にデータの検索をやり直していますので、検索用の SQL 文を記録する必要があります。11行目から14行は、表示するデータがある場合。15行目以下は、表示するデータが何もないになります。ここでは、データがある場合をみましょう。
 11行目は検索の実行です。開始ページ-この場合は0-から、1ページに表示する項目数-この場合は10-件分を、SQL を使用して検索しています。12行目で、現在の表示ページのインデックス(0ベース)を、13行目で、現在のページ番号(1ペース)を、14行目で選択中の知識番号のクリアを行っています。検索した直後ですから、選択中の知識はありません。

 おおよその感じはつかめましたでしょうか?

更新の処理

 更新ボタンの xhtml ファイルを示します。(部分)


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="updateButtonOn" class="btn btn-primary hidden" type="submit" value="更 新" action="#{knowledgeListBean.update()}" />
        <input id="updateButtonOff" class="btn btn-primary" type="button" value="更 新" disabled="disabled" />
    …
    </form>                 

                

 更新ボタンが2つあるのは、ボタンが有効な場合と無効な場合とで切り替えているためです。その切替えは jQuery で行っています。上のボタンが有効な場合で、下のボタンが無効で表示のみさせたい場合です。

  update メソッドを見てみましょう。

                    
    public String update()
    {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        return "updateKnowledgePage.xhtml?faces-redirect=true&id=" + selectedId;
    }                        

                

 現在のセッション情報を無効にし、 updateKnowledgePage.xhtml に遷移しています。そのときパラメータ id に現在選択されている知識番号を渡しています。これを渡さないと、どの知識を編集してよいか分かりませんから当然ですね。

削除の処理

 削除ボタンの xhtml ファイルを示します。(部分)


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="deleteButtonOn" class="btn btn-primary hidden" type="submit" value="削 除" action="#{knowledgeListBean.remove()}" />
        <input id="deleteButtonOff" class="btn btn-primary" type="button" value="削 除" disabled="disabled" />
    …
    </form>                 

                

 削除ボタンが2つあるのは、ボタンが有効な場合と無効な場合とで切り替えているためです。その切替えは jQuery で行っています。上のボタンが有効な場合で、下のボタンが無効で表示のみさせたい場合です。

  remove メソッドを見てみましょう。

                    
    public String remove()
    {
        FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
        return "removeKnowledgePage.xhtml?faces-redirect=true&id=" + selectedId;
    }    
                

 現在のセッション情報を無効にし、 removeKnowledgePage.xhtml に遷移しています。そのときパラメータ id に現在選択されている知識番号を渡しています。これを渡さないと、どの知識を削除してよいか分かりません。

追加専用画面

 初期画面で「新規」ボタンをクリックすると、この画面に遷移します。この画面の xhtml ファイルと管理 Bean クラスは、 newKnowledgePage.xhtml と NewKnowledgeBean です。

クラス定義と主な変数

                     
@Named(value = "knowledgeBean")
@SessionScoped
public class NewKnowledgeBean extends BaseBean implements Serializable {

   protected Knowledge knowledge;
    
    @Inject    
    protected MajorDao majorDao;
    
    protected List<Major> majors;
    
    @Inject
    protected MinorDao minorDao;
    
    protected List<Minor> minors;
    
    @Inject
    protected KnowledgeDao knowledgeDao;
}                     

                 

 いままでの管理 Bean と異なる点は、@Named(value = "knowledgeBean") としていることです。 EL 式のルールでは、この管理 Bean を記述するには newKnowledgeBean としなければいけません。しかしこの名前ではぴったりしていないと感じ、 EL 式では knowledgeBean と記述したいと思いました。

 次は、@SessionScoped です。インスタンス変数に Knowledge クラスのインスタンスを定義しています。これは新規登録中の知識を表現したものです。この変数は、この画面が有効である期間ずっと有効であってほしいため、セッションスコープとしています。

 それ以外の変数は説明がなくても了解できると思います。

初期化処理

 初期化処理を示します。

                    
    @PostConstruct
    public void initialize()
    {
        knowledge = new Knowledge();
        knowledge.setMajorID("-1");
        knowledge.setMinorID("-1");

        clear();
        
        majors = select(majorDao.findAll());        
    }                  

                

 インスタンス変数の knowledge の生成とその初期化をし、大分類の全件検索を行っています。

 クラス定義で @SessionScoped としていますので、この初期化メソッドは1度だけ呼び出されます。ブラウザからの要求があるたびに毎回呼び出されては、 knowledge 変数の意味がなくなりますね。

追加時の処理

  xhtml ファイルの保存ボタンの内容を示します(部分)。


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="okButton" class="btn btn-primary" type="submit" value="保 存" action="#{knowledgeBean.add()}" />
    …
    </form>  

                 

  add メソッドを見てみましょう。クラス定義で @Named(value = "knowledgeBean") としていますので、クラスは NewKnowledgeBean となります。


    public String add()
    {
        List<Executable> list = new ArrayList<>();
        list.add(knowledgeDao.insert(knowledge));
        Boolean flag = transact(list);
 
        return flag ? "knowledgeList.xhtml" : "newKnowledgePage.xhtml";        
    }    

                 

 インスタンス変数の knowledge をデータベースに追加しています。データの必須検査や長さの検査はブラウザ上で jQuery で行っています。サーバでは検査していません。

 ここで特徴的なことは、データベース操作の結果に応じて遷移先を変えていることです。エラーが検出された場合は、そのエラー内容を表示した同じページから移動しません。データの追加が成功した場合には、初期画面に遷移することになります。

編集専用画面

 初期画面で「検索」ボタンをクリックして検索実行後、検索結果の一覧から任意の行を選択し、「更新」ボタンをクリックすると、この画面に遷移します。この画面の xhtml ファイルと管理 Bean クラスは、 updateKnowledgePage.xhtml と updateKnowledgeBean です。

クラス定義と主な変数

                
@Named
@SessionScoped
public class UpdateKnowledgeBean extends BaseBean implements Serializable {  
   @Inject
    private MajorDao majorDao;

   protected List<Major> majors;
       
    @Inject
    private MinorDao minorDao;
    
    protected List<Minor> minors;
    
    @Inject
    private KnowledgeDao knowledgeDao;
      
    private Knowledge knowledge;

    private String id;
}                    

            

  NewKnowledgeBean クラスとは違って @Named で EL 式用の別名を定義していません。学習用サンプルということで大目に見てください。業務の場合は統一した方がよいでしょう。セッションスコープを採用しているのは、編集対象となる変数 knowledge を画面が有効となる期間保持したいためです。

 多くの変数はその変数名から役割が理解できると思います。変数 id は GET パラメータで渡された知識番号用です。

初期化処理

 初期化処理を示します。全大分類を読みだしているだけです。

                    
    @PostConstruct
    public void initialize()
    {
        majors = select(majorDao.findAll());
    }

                

 この画面ではもう一つ重要な初期化処理があります。それは GET パラメータで渡される知識番号の知識を、最初から編集状態としないといけないということです。

 そのための xhtml ファイル(部分)を示します。この部分は head タグの直前に置いています。

                    
    <f:metadata>
        <f:viewParam name="id" value="#{updateKnowledgeBean.id}" />
        <f:viewAction action="#{updateKnowledgeBean.init}"/>
    </f:metadata>      

                

  f:viewParam により GET のパラメータ id の値を UpdateKnowledgeBean の id 変数に設定しています。次に f:viewAction により init メソッドを呼び出しています。では次に、 init メソッドを見てみましょう。

                    
    public void init()
    {
        Integer num;
        try {
            num = Integer.parseInt(id);
            // preRenderViewを使用した場合に必要な処理
            // preRenderViewは再描画の度に呼び出されるため、必要な場合のみに限定するための処理
            // これがないと、大分類を変更したときの処理が正しくない
            //if (knowledge == null || ! num.equals(knowledge.getId()))
            //{
                knowledge = (Knowledge) select(knowledgeDao.uniqueKey(num));
                minors = select(minorDao.findByMid(getMajorId()));
           // }
        } catch (Exception e) { }
    }                        

                

 処理の中心は、 GET パラメータの id を数値に変更し、その番号を持つ知識を検索することです。ただし、ソースにコメントとして書かれていることは重要です。今回の例では、 f:viewParam f:viewAction を利用しています。以前は preRenderView を使用しました。この2つには動作に違いがあります。 preRenderView はポストバックの度に繰り返し呼び出されます。注意が必要ですね。

更新時の処理

 更新ボタンの xhtml を示します。(部分)


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="updateButton" class="btn btn-primary" type="submit" value="保 存" action="#{updateKnowledgeBean.update()}" />
    …
    </form>                          

                

  update メソッドを見てみましょう。

                    
    public String update()
    {
        List<Executable> list = new ArrayList<>();
        list.add(knowledgeDao.update(knowledge));
        Boolean flag = transact(list);
        
        return flag ? "knowledgeList.xhtml?faces-redirect=true" : "updateKnowledgePage.xhtml";        
    }                        

                

 編集中の知識をデータベースに更新しています。処理が正常終了したか、エラーが発生したかによって遷移先を変えています。正常終了時は、 knowledgeList.xhtml に遷移します。エラーが発生した場合は、そのエラー情報を表示して遷移しません。

削除専用画面

 初期画面で「検索」ボタンをクリックして検索実行後、検索結果の一覧から任意の行を選択し、「削除」ボタンをクリックすると、この画面に遷移します。この画面の xhtml ファイルと管理 Bean クラスは、 removeKnowledgePage.xhtml と RemoveKnowledgeBean です。

クラス定義と主な変数

                    
@Named
@SessionScoped
public class RemoveKnowledgeBean extends BaseBean implements Serializable {

    @Inject
    private MajorDao majorDao;
    
    @Inject
    private MinorDao minorDao;
    
    @Inject
    private KnowledgeDao knowledgeDao;
    
    private Major major;
    
    private Minor minor;
    
    private Knowledge knowledge;

    private String id;
}                       

                

初期化処理

  GET のパラメータとして渡される知識を表示して、削除の初期画面を作る必要があります。そのための、 xhtml を示します。(部分)この部分は head タグの直前に起きました。また編集の画面とは異なり、この画面では preRenderView を利用しました。何事も勉強です。

                    
    <f:metadata>
        <f:viewParam name="id" value="#{removeKnowledgeBean.id}" />
        <f:event type="preRenderView" listener="#{removeKnowledgeBean.preRender}"/>    
    </f:metadata>

                

  f:viewParam により GET のパラメータ id からの読みだしを自動化しています。次は、 preRender メソッドを見てみましょう。

                    
    public void preRender()
    {
        Integer num;
        try {
            num = Integer.parseInt(id);
            knowledge = (Knowledge) select(knowledgeDao.uniqueKey(num));                
            major = (Major) select(majorDao.uniqueKey(knowledge.getMajorID()));
            minor = (Minor) select(minorDao.uniqueKey(new MinorPK(knowledge.getMajorID(), knowledge.getMinorID())));
        } catch (Exception e) {
            knowledge = new Knowledge();
            major = new Major();
            minor = new Minor();
        }
    }                        

                

  GET パラメータの値を数値に変換し、該当する知識を検索しています。但し、削除の画面ではポストバックを考慮する必要がありません。そのため、編集の画面で preRenderView を使った場合の問題は起こりません。

削除時の処理

 削除ボタンの xhtml を示します。(部分)


    <form jsfc="h:form">
    …
        <input jsfc="h:commandButton" id="removeButton" class="btn btn-primary" type="submit" value="削 除" action="#{removeKnowledgeBean.remove()}" />
    …
    </form>

                

 次に、 remove メソッドを見てみましょう。

                    
    public String remove()
    {
        Integer num;
        try {
            num = Integer.parseInt(id);
            List<Executable> list = new ArrayList<>();
            list.add(knowledgeDao.delete(num));
            Boolean flag = transact(list);
           
            return flag ? "knowledgeList.xhtml" : "removeKnowledgePage.xhtml";
            
        } catch (Exception e) { }
        
        return "removeKnowledgePage.xhtml";
    }                  

                

 知識番号を指定して、データベースから削除しています。処理の結果によって、遷移先を変更しています。正常終了の場合 knowledgeList.xhtml に遷移します。エラーの場合、エラー内容を表示して遷移しません。

 勉強しながら作成していったため、編集の画面と削除の画面では違いがあります。編集の画面の方が洗練されています。