‘EJB’ カテゴリーのアーカイブ

rich:dropDownMenuで動的メニューの生成

2011年8月8日 月曜日

Seam RichFacesのDropDownMenuは、RichFacesのコンポーネントの中でもどうしても使いたい機能のひとつだけど、なかなかどうして一筋縄ではいかなかった。

公式サイトのサンプルにあるように、普通に静的メニューを作るならば問題無い。

しかし業務システムなので当然データベースから動的に生成したいのだけど、サンプルのようには動かなかった。

まず常套手段のコード

<rich:toolBar>

<rich:dropDownMenu event=”onclick”>

<f:facet name=”label”>

<h:outputText value=”File” />

</f:facet>

<a4j:repeat value=”#{dropDownMenuBean.items}” var=”item”>

<rich:menuItem submitMode=”ajax” value=”#{item}” />

</a4j:repeat>

</rich:dropDownMenu>

<rich:dropDownMenu>

<f:facet name=”label”>

<h:outputText value=”Edit”/>

</f:facet>

</rich:dropDownMenu>

</rich:toolBar>

a4j:repeatがui:repeatでもdataTableでも動かない。

で、以下が正解のコード

<rich:toolBarGroup>

<c:forEach var=”cates” items=”#{dropCategorys}” varStatus=”pri”>

<rich:dropDownMenu>

<f:facet name=”label”>

<h:panelGroup>

<h:outputText value=”#{cates}” />

</h:panelGroup>

</f:facet>

<c:forEach var=”menus” items=”#{dropItems[pri.index]}” varStatus=”sec”>

<rich:menuItem submitMode=”none” styleClass=”menuLink” value=”#{menus}”>

<s:link view=”#{dropUrls[pri.index][sec.index]}” />

</rich:menuItem>

</c:forEach>

</rich:dropDownMenu>

</c:forEach>

<rich:jQuery selector=”.menuLink” query=”click(function() {location.href = jQuery(this).find(‘a’).attr(‘href’) })”/>

</rich:toolBarGroup>

sessionBean部分 List<MenuCategory> tmpCategorys = entityManager.createQuery(“SELECT m FROM MenuCategory AS m WHERE m.active =¥

:active ORDER BY m.indication”)

.setParameter(“active”,true)

.getResultList();

int cateSize = tmpCategorys.size();

int menuSize = 0;

dropCategorys = new String[cateSize];

dropItems = new String[cateSize][];

dropUrls = new String[cateSize][];

for(int s = 0;s<cateSize;s++) {

dropCategorys[s] = tmpCategorys.get(s).getNameja();

List<Menu> menus = tmpCategorys.get(s).getMenu();

menuSize = menus.size();

dropItems[s] = new String[menuSize];

dropUrls[s] = new String[menuSize];

for(int q = 0;q<menuSize;q++) {

dropItems[s][q] = menus.get(q).getNameja();

dropUrls[s][q] = menus.get(q).getUrl();

}

}

RichFacesのバグ?とにかくui:repeatは捨てて、c:forEachでやる。

さらにList<>やArrayList<>ではなく、配列をわたさなきゃならないらしい。

分かればどうってことないけど、リソースも無いし結構はまりました。

seamとIE6

2011年8月8日 月曜日

IE6はRichFacesのSkinスタイルを思いっきり無視するので要注意。

特にcommandButtonとcheckBoxは「ただのinput」とみなされ、色もマージンも妙なコトになる。

theme.cssをみると

input, textarea {

border: 1px solid #BBBBBB;

font-size: 12px;

background: #F0F8FF;

color: black;

}

input[type="submit"], input[type="button"],.hinput {

background: #4477AA;

color: white;

margin: 5px;

padding: 2px; border-color: gray;

}

こうなっている。

つまり

1.input指定でinput要素全てに対してスタイルを設定。

2.input[type=""]で、特定の要素(ここではbotton、submit)のスタイルをオーバーライド。

という流れ。

つまり、IE6は2のinput[type=""]を無視するわけだ。

しかし、様々なCSSハックを行っても何故か状況は変わらず…(通常のHTMLではうまくいった)。

結局faceletsに

<style>

.input_class {

border: 1px solid #BBBBBB;

font-size: 10px;

background: #F0F8FF; color: black;

}

.check_class {

background-color: transparent;

border: none;

margin-left: 5px;

margin-right: 5px;

}

.button_class {

background: #4477AA;

color: white;

margin-left: 5px;

margin-right: 5px;

padding: 2px;

border-color: gray;

}

</style>

のようにべた書きして、各要素でstyleClassを使って強制指定することで、ようやく解決した。

う~ん…Vistaがずっこけたので、企業ではいまだにXPがほとんど。

アップデートを意図的に抑制している企業も多くて、IE6はまだ無視出来ない。

PHPやHTML開発でもそうだがIE6には、もうウンザリ。

いつまでWEB開発者は、こんな無駄な時間を浪費せざるおえないのだろうか…。

seamでのファイルアップロード&ダウンロード

2011年8月8日 月曜日

seamでファイルのアップロードにはs:fileUploadを使う。

これを使うと、EntityBeanに簡単にinsert出来る。

view部分

<h:form id=”setFORM” enctype=”multipart/form-data”>

<h:outputText value=”#{workfile.filename}” />

<s:fileUpload id=”uploadFile” data=”#{workfile.data}” fileName=”#{workfile.filename}” />

<h:commandButton id=”save” value=”登録” action=”#{fileEntryAction.save}” />

</h:form>

sessionBean部分

public String save() {

em.persist(workfile);

}

entityBean部分

public class Workfile implements java.io.Serializable {

@Id

@GeneratedValue

private Long workfile;

private String filename;

@Lob

@Column(length = 2147483647)

@Basic(fetch = FetchType.LAZY)

private byte[] data;

~ getter/setter 省略 ~

}

わずか数分でコーディング完了。

次にEntityBeanにbyte[]で登録したファイルのダウンロード。

s:linkでリンクをはっておき

<th><h:outputLabel value=”図面:” /></th>

<td><s:link action=”#{restoreAction.download(item.workfile)}” value=”#{item.workfile.filename}” /></td></tr>

該当action部分

public void download(Workfile workfile) {

try {

ExternalContext externalContext = facesContext.getExternalContext();

HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

response.setContentType(“application/octet-stream”);

response.setHeader(“Content-Disposition”,”attachment; filename=¥”"+workfile.getFilename()+”¥”");

ServletOutputStream os = response.getOutputStream();

os.write(workfile.getData());

os.flush();

os.close();

facesContext.responseComplete();

} catch(Exception ex) { }

}

これでブラウザにダウンロードダイアログが表示される。

日本語ファイル名の場合は、ダウンロード時にファイル名の文字コードを変換すればよい。

とっても便利なModalPanel

2011年8月8日 月曜日

SeamのRichFacesで最も便利な機能ModalPanel。

これを使いたくてSeamを使ってると言っても良いかも。

ただし、便利に使うには少々コツが必要。意外にリソースが少なかったりするからメモっておく。

普通に上位10件程度の履歴などを見せる場合には、特にコツもなく以下のようなコードで出来る。

パネルを開く

<h:outputLink value=”#” id=”show_link”>

<h:graphicImage url=”img/historys-mini.gif” alt=”履歴” border=”0″ />

<rich:componentControl for=”history_panel” attachTo=”show_link” operation=”show” event=”onclick”  />

<a4j:support event=”onclick” actionListener=”#{orderEntryAction.getHistorys(row)}” reRender=”hispanel” />

</h:outputLink>

パネル部分

<rich:modalPanel id=”history_panel” width=”550″ height=”400″>

<f:facet name=”header”>

<h:panelGroup>

<h:outputText value=”購買履歴(最近の10件):” />

</h:panelGroup>

</f:facet>

<f:facet name=”controls”>

<h:panelGroup>

<h:graphicImage value=”img/wclose.png” style=”cursor:pointer” id=”hidelink”/>

<rich:componentControl for=”history_panel” attachTo=”hidelink” operation=”hide” event=”onclick” />

</h:panelGroup>

</f:facet>

<rich:dataTable id=”hispanel” var=”sublists” value=”#{orderHistoryLists}” border=”0″ cellpadding=”0″ cellspacing=”0″ rowKeyVar=”subrow”>

<rich:column styleClass=”cell_no”>

<f:facet name=”header”>

<h:outputText value=”No:” />

</f:facet>

<h:outputText value=”#{subrow+1}” />

</rich:column>

<rich:column>

<f:facet name=”header”>

<h:outputText value=”年月日:” />

</f:facet>

<h:outputText value=”#{sublists.orders.bdate}”>

<s:convertDateTime pattern=”yyyy/MM/dd HH:mm” />

</h:outputText>

</rich:column>

<rich:column styleClass=”cell_number”>

<f:facet name=”header”>

<h:outputText value=”数量:” />

</f:facet>

<h:outputText value=”#{sublists.num}” />

</rich:column>

<rich:column>

<f:facet name=”header”>

<h:outputText value=”金額:” />

</f:facet>

<h:outputText value=”#{sublists.price}” />

</rich:column>

</rich:dataTable>

</rich:modalPanel>

特に難しくもなく、RichFacesのマニュアル通りに書けば普通に動く。

rich:modalPanelのコードは、どこに書いてもOKなので、ページの頭にでも書いておけばOK。

ただし、ModalPanelの中でフォームを使って検索などをしたい場合には書き方が変わる。

例えば、メインでリンクをクリック->モーダルオープン->商品や顧客などを検索->検索結果をメインにセット等という業務システムではあるととても助かる機能を実装したい場合、上記の方法だと何故かフォームの変数がSessionBeanに渡らない。

なので以下のように書く

パネルを開く

(上記といっしょ。省略)

パネル部分

<ui:include src=”ItemsearchPanel.xhtml” />

として別のfaceletファイルを読み込む。ui:includeは必ずa4j:formの外側で行う。何故ならItemsearchPanelの中でもa4j:formを使うから。

そしてItemsearchPanelActionクラスを作成(無論インターフェースも)。

@Stateful

@Name(“itemsearchPanelAction”)

public class ItemsearchPanelAction implements Serializable, IItemsearchPanel {

@PersistenceContext(type = EXTENDED)

private EntityManager em;

@In(required = false)

private String searchcd;

@In(required = false)

private String searchname;

@DataModel(value = “searchItemLists”)

private List<Item> searchItemLists;

@Out(required = false)

private Item item;

@In

private FacesMessages facesMessages;

@In

private Events events;

@Logger

private Log log;

Query query;

@Remove

@Destroy

public void destroy() {

}

public void searchItem() {

try {

if(searchcd.equals(“”)) {

query = em.createQuery(“SELECT i FROM Item AS i WHERE :searchname IS NULL OR (i.nameja LIKE :searchname OR i.namekana LIKE :searchname OR i.modelname LIKE :searchname OR i.modelkana LIKE :searchname OR i.makername LIKE :searchname OR i.standards LIKE :searchname )”);

query.setParameter(“searchname”,getSearchPattern(searchname));

} else {

query = em.createQuery(“SELECT i FROM Item AS i WHERE i.item = :searchcd”);

query.setParameter(“searchcd”,Long.parseLong(searchcd));

}

query.setFirstResult(0);

query.setMaxResults(8);

searchItemLists = query.getResultList();

} catch(NoResultException ex) {

facesMessages.add(“商品がありません”);

}

}

public String getSearchPattern(String word) { return word == null ? “%” : ‘%’ + word.toLowerCase().replace(‘*’, ‘%’) + ‘%’;

}

view部分

<ui:composition xmlns=”http://www.w3.org/1999/xhtml”

xmlns:s=”http://jboss.com/products/seam/taglib”

xmlns:ui=”http://java.sun.com/jsf/facelets”

xmlns:f=”http://java.sun.com/jsf/core”

xmlns:c=”http://java.sun.com/jsp/jstl/core”

xmlns:h=”http://java.sun.com/jsf/html”

xmlns:a4j=”http://richfaces.org/a4j”

xmlns:rich=”http://richfaces.org/rich”

template=”layout/template3.xhtml”>

<ui:component>

<rich:modalPanel id=”search_panel” width=”550″ height=”400″>

<f:facet name=”header”>

<h:panelGroup>

<h:outputText value=”商品の検索:” />

</h:panelGroup>

</f:facet>

<f:facet name=”controls”>

<h:panelGroup>

<h:graphicImage value=”img/wclose.png” style=”cursor:pointer” id=”search_hide” />

<rich:componentControl for=”search_panel” attachTo=”search_hide” operation=”hide” event=”onclick” />

</h:panelGroup>

</f:facet>

<a4j:form ajaxSubmit=”true”>

<h:panelGrid border=”0″ columns=”5″>

<h:panelGroup>

<h:outputLabel value=”商品コード:” />

</h:panelGroup>

<h:panelGroup>

<h:inputText value=”#{searchcd}” size=”5″ maxlength=”255″ style=”ime-mode:disabled;” />

</h:panelGroup>

<h:panelGroup>

<h:outputLabel value=”キーワード:” />

</h:panelGroup>

<h:panelGroup>

<h:inputText value=”#{searchname}” size=”30″ maxlength=”255″ />

<br />

<h:outputText value=”注)品名、型式、メーカー、サイズ/仕様であいまい検索” />

</h:panelGroup>

<h:panelGroup>

<a4j:commandButton value=”検索” actionListener=”#{itemsearchPanelAction.searchItem}” reRender=”slists” styleClass=”button_class” />

</h:panelGroup>

</h:panelGrid>

<rich:dataTable id=”slists” var=”slists” value=”#{searchItemLists}” border=”0″ cellpadding=”0″ cellspacing=”0″ rowKeyVar=”srow”>

<rich:column styleClass=”cell_no”>

<f:facet name=”header”>

<h:outputText value=”No:” />

</f:facet>

<h:outputText value=”#{srow+1}” />

</rich:column>

<rich:column>

<f:facet name=”header”>

<h:outputText value=”商品名:” />

</f:facet>

<h:outputText value=”#{slists.nameja}” />

</rich:column>

<rich:column>

<f:facet name=”header”>

<h:outputText value=”型番:” />

</f:facet>

<h:outputText value=”#{slists.modelname}” />

</rich:column>

<rich:column styleClass=”cell_center”>

<f:facet name=”header”>

<h:outputText value=”設定:” />

</f:facet>

<a4j:commandButton value=”設定” actionListener=”#{orderEntryAction.getItem(comming,slists)}” reRender=”main” id=”search_set” styleClass=”button_class” />

<rich:componentControl for=”search_panel” attachTo=”search_set” operation=”hide” event=”onclick” />

</rich:column>

</rich:dataTable>

</a4j:form>

</rich:modalPanel>

</ui:component>

</ui:composition>

ここまで来ればすぐ実装出来るはずだけど、a4j:formをつい忘れてしまうので要注意かな。

昨今はブラウザ側がポップアップブロックを標準装備しててJavaScriptのwindow.open()も使えないから、これは必須の機能だね。

もう気に入っちゃってガンガン多投してるけど、凄く表現力上がるし客受けも良い。

こういうのをパッと出来ちゃう所がseamの便利なところだなぁ。

SeamでのCSV出力

2011年8月8日 月曜日

Seam(JSF)でCSVを出力する場合、facesContextを利用してファイルデータを書き出す。

String encoding = “Windows-31J”;

String filename = “order.csv”;

ExternalContext externalContext = facesContext.getExternalContext();

HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

response.setContentType(“application/octet-stream; charset=”+encoding);

response.setHeader(“Content-Disposition”,”attachment; filename=¥”"+filename+”¥”");

response.setCharacterEncoding(encoding);

try {

PrintWriter out = response.getWriter();

out.print(“aaaa”,”bbbb”,”cccc”,”dddd”);

out.print(“¥n”);

out.close();

facesContext.responseComlete(); } catch (IOException ex) {

}

このへんの処理も業務システムでは多投する。

もう少しスマートに出来るような仕組みがフレームワーク側にあると便利なのだが…。

JAVA_OPTS

2011年8月8日 月曜日

Jboss AS は、デフォルト設定のままでは、deploy時にOutOfMemoryだしまくり。

Linux環境ではrun.conf、Winではrun.batのJAVA_OPTS変更しないと開発では利用出来ない。

JAVA_OPTS=”-Xms1303m -Xmx1303m -XX:PermSize=256m -XX:MaxPermSize=512m” Jboss ASはメモリイーターなので、けっこう多めに振っておいた方が安心? メモリ搭載量の少ないマシンに多めのメモリを振りすぎても、やはりOutOfMemory…。 自分の環境では、いちどにたくさんのデータを取得するようなページでは752以上ないとダメだった。 パス式でHQLを書くなどのチューニングまでは(まだ)いっていないが、OS領域もあわせると最低1GB以上は必要かも。

RedHatの公式サポートなどを受けると、この辺のラインはキチンとだしてくれるのだろうか?

とりあえずデュアルコアのプロセッサとタップリなメモリ、それとなるだけメモリを消費しないようなソースで、この仕事をしのいでいこう。 ハードは安くなったからね。

リダイレクト先の選択

2011年8月8日 月曜日

seamで、条件によって処理後のリダイレクト先を切替える場合はpages.xmlを利用すると良い。

<page view-id=”/Recognition.xhtml” action=”#{recognitionAction.setHome}” /> 上記をpages.xmlに書き込んだ場合、view-idのページがレンダリングされる直前にactionメソッドが呼び出される。

なので、あとはBeanに

if(come.equals(“homeAction”)) {

return “/home.xhtml”;

} else {

return “/RecognitionList.xhtml”;

}

とでも書いておけばOK。

この辺の処理は、本番業務システムではよく必要とする。 けど、自前で書くとけっこう面倒。 フレームワークが面倒を見てくれるととても助か

Skinカラー

2011年8月8日 月曜日

seamでRichFacesを利用している場合、Skinの色にアクセスしたい場合は

<style>

.r_header {

border: 1px solid #{a4jSkin.tableBorderColor};

background-color: #{a4jSkin.headerGradientColor};

}

</style>

のようにa4jSkinで、Skinのスタイルにアクセス出来る。 自前のテーブルやパーツをスタイリングする場合に、普通にcolor:#BCBCBCなどと書いてしまうと、Skinを変更したとき色が合わずに悲しい結果になってしまう。 利用できるスタイルはRichFacesのAPIガイド->Skinを参照。 上記スタイルはfacelets中に書くべし。

別スタイルシートファイルに書いてもダメだった(まぁ当たり前だが…)。

テンプレにスタイルが入るのは気持ち悪いけど、JSFはもともとそれほど綺麗なHTMLを生成してくれる訳ではないので割り切っていく。

改行の出力

2011年8月8日 月曜日

inputTextaraなどで入力させた文字列を表示させるには、s:formattedTextとpreを組み合わせると便利。

<pre>

<s:formattedText value=”#{testBean.textarea}” />

</pre>

preを入れないと、普通に出力されてしまう。 単純だが以外にハマッた。 改行をbrに置き換えるメソッドなどをゴリゴリ書いてて、「まてよ?」と。 なのでメモっておく。

コンボボックスの連携

2011年8月8日 月曜日

あるコンボボックスの選択結果によって、次のコンボボックスを動的に変化させたいような場合がある。

seamの場合はa4j:supportタグを利用すると良さそう。

<h:selectOneMenu value=”#{machinesListAction.sectioncd}”>

<s:selectItems value=”#{machinesSections}” var=”sections” label=”#{sections.nameja}” />

<s:convertEntity /> <a4j:support event=”onchange” reRender=”linecd” ajaxSingle=”true” action=”#{machinesListAction.getMachinesSrLine}” />

</h:selectOneMenu>

<h:selectOneMenu value=”#{machinesListAction.linecd}” id=”linecd”>

<s:selectItems value=”#{machinesLines}” var=”lines” label=”#{lines.nameja}” /

<s:convertEntity />

<a4j:support event=”onchange” reRender=”main” action=”#{machinesListAction.getMachinesLists}” />

</h:selectOneMenu>

とっても簡単。 ただし、SessionBean側で@Beginをしておかないと、JSFのライフサイクルエラーが出てうまくいかなかった。 どうやらフォーム系は全て@Beginと@Endでライフサイクルを意識した方が良さそう。