きしだのはてな このページをアンテナに追加 RSSフィード

2016-01-30(土) SpringBootでPrimefacesのFileUploadを使う

[][][][] SpringBootでPrimefacesのFileUploadを使う 04:08  SpringBootでPrimefacesのFileUploadを使う - きしだのはてな を含むブックマーク

すげーハマった。

正確にはSpring MVCと一緒に使ったとき。


いまのところこんな感じ

  • SpringApplication.runしたときに@ManagedBeanが効く設定がわからない
  • なのでConverterでDIができない(DIさせるにはConverterはManagedBeanにしてconverterId指定じゃなくbinding指定する必要がある)
  • Converterの登録にはfaces-config.xmlを使う
  • Spring MVCServlet 3.0 File Uploadが使えない。のでCommons FileUploadを使う。
  • onStartUpメソッドが呼ばれてない

onStartupが呼ばれてないというのがクセモノで、Overrideしてるし当然よばれてるだろうと思ってたら呼ばれてなくて、commonsを使うという指定が効いてなくてハマった。


Applicationクラスはこう

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer implements ServletContextAware{
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("never invoked");
    }
    
    @Override
    public void setServletContext(ServletContext servletContext) {
        servletContext.setInitParameter("com.sun.faces.forceLoadConfiguration", Boolean.TRUE.toString());
        servletContext.setInitParameter("primefaces.UPLOADER", "commons");
        servletContext.addListener(new RequestContextListener());
    }

    @Bean
    public ServletRegistrationBean facesServletRegistration() {
        FacesServlet servlet = new FacesServlet();
        ServletRegistrationBean registration = new ServletRegistrationBean(servlet, "*.xhtml");
        registration.setLoadOnStartup(1);
        return registration;
    }

    @Bean
    public ServletListenerRegistrationBean<ConfigureListener> jsfConfigureListener() {
        return new ServletListenerRegistrationBean<>(new ConfigureListener());
    }
    @Bean
    public FilterRegistrationBean filterBean(){
        FilterRegistrationBean frb = new FilterRegistrationBean();
        frb.setFilter(new FileUploadFilter());
        frb.addServletRegistrationBeans(facesServletRegistration());
        return frb;
    }
}

あと、faces-config.xmlをresources/META-INFに

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                                  http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" 
              version="2.2">
  <application>
    <el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
  </application>
 
</faces-config>

2013-08-06(火) PrimeFacesでラベルやパネルの表示非表示を切り替える

[][][]PrimeFacesでラベルやパネルの表示非表示を切り替える 03:42 PrimeFacesでラベルやパネルの表示非表示を切り替える - きしだのはてな を含むブックマーク

JSFではrendered属性でコンポーネントの表示・非表示を切り替えることができます。

f:id:nowokay:20130807033635p:image


ただ、Ajax動作で、例えばPrimeFacesのチェックボックスやコンボボックスで表示・非表示を切り替えようとすると、単純には切り替えできません。

たとえば、チェックボックスで切り替えようと思えば、素直に書くと次のようになります。

<p:selectBooleanCheckbox value="#{switching.sw}"
                         itemLabel="上を表示">
    <p:ajax update="lblUp lblDown"/>
</p:selectBooleanCheckbox>
<br/>
<h:outputText id="lblUp" value="上" rendered="#{switching.sw}"/>
<h:outputText id="lblDown" value="下" rendered="#{!switching.sw}"/>

チェックボックスがswitching.swと連動し、それぞれのoutputTextのrenderedにswitching.swの正負が指定されているので、これでチェックボックスとテキストの表示非表示が連動しそうに思います。


ところが、これでは表示が切り替わりません。


次のように、切り替えるコンポーネントをp:outputPanelに入れる必要があります。

<p:selectBooleanCheckbox value="#{switching.sw}"
                         itemLabel="上を表示">
    <p:ajax update="lblUp lblDown"/>
</p:selectBooleanCheckbox>
<br/>
<p:outputPanel id="lblUp">
<h:outputText value="上" rendered="#{switching.sw}"/>
</p:outputPanel>
<p:outputPanel id="lblDown">
<h:outputText value="下" rendered="#{!switching.sw}"/>
</p:outputPanel>

ここで、renderedは表示・非表示を切り替えるコンポーネントに、ただしajaxのupdate指定はoutputPanelコンポーネントに対して行います。


コンボボックスでのパネルの切り替えも同様です。

<p:selectOneMenu value="#{switching.str}" >
    <p:ajax update="pnlLeft pnlRight"/>
    <f:selectItem itemLabel="選択してください" itemValue=""/>
    <f:selectItem itemLabel="左" itemValue="left"/>
    <f:selectItem itemLabel="右" itemValue="right"/>
</p:selectOneMenu>
<p:outputPanel id="pnlLeft">
<p:panelGrid columns="1" rendered="#{switching.str == 'left'}">
    <h:outputText value="左"/>
    <h:outputText value="←"/>
</p:panelGrid>
</p:outputPanel>
<p:outputPanel id="pnlRight">
<p:panelGrid columns="1" rendered="#{switching.str == 'right'}">
    <h:outputText value="右"/>
    <h:outputText value="→"/>
</p:panelGrid>
</p:outputPanel>

管理ビーンは次のようになります。

package kis;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ViewScoped
@ManagedBean
public class Switching implements Serializable{
    private boolean sw;
    public boolean isSw() {
        return sw;
    }
    public void setSw(boolean sw) {
        this.sw = sw;
    }

    private String str = "";
    public String getStr() {
        return str;
    }
    public void setStr(String str) {
        this.str = str;
    }
    
}

Facelet全体は次のようになっています。

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://java.sun.com/jsf/core">
    <h:head>
        <title>表示切り替え</title>
    </h:head>
    <h:body>
        <h:form id="fm">
            <p:selectBooleanCheckbox value="#{switching.sw}"
                                     itemLabel="上を表示">
                <p:ajax update="lblUp lblDown"/>
            </p:selectBooleanCheckbox>
            <br/>
            <p:outputPanel id="lblUp">
            <h:outputText value="上" rendered="#{switching.sw}"/>
            </p:outputPanel>
            <p:outputPanel id="lblDown">
            <h:outputText value="下" rendered="#{!switching.sw}"/>
            </p:outputPanel>
            <br/>
            <p:selectOneMenu value="#{switching.str}" >
                <p:ajax update="pnlLeft pnlRight"/>
                <f:selectItem itemLabel="選択してください" itemValue=""/>
                <f:selectItem itemLabel="左" itemValue="left"/>
                <f:selectItem itemLabel="右" itemValue="right"/>
            </p:selectOneMenu>
            <p:outputPanel id="pnlLeft">
            <p:panelGrid columns="1" rendered="#{switching.str == 'left'}">
                <h:outputText value="左"/>
                <h:outputText value="←"/>
            </p:panelGrid>
            </p:outputPanel>
            <p:outputPanel id="pnlRight">
            <p:panelGrid columns="1" rendered="#{switching.str == 'right'}">
                <h:outputText value="右"/>
                <h:outputText value="→"/>
            </p:panelGrid>
            </p:outputPanel>
        </h:form>
    </h:body>
</html>

2013-08-03(土) PrimeFacesのファイルダウンロードを条件によってエラーにする

[][][]PrimeFacesのファイルダウンロードを条件によってエラーにする 00:37 PrimeFacesのファイルダウンロードを条件によってエラーにする - きしだのはてな を含むブックマーク

PrimeFacesにはファイルダウンロードのコンポーネントがあって、これは非常に便利なのですが、Ajax動作ができないのでダウンロードすべきデータがないときなどのエラー処理ができません。

そこで、別ボタン経由でダウンロード開始を行ってエラー処理を行うようにしてみます。

f:id:nowokay:20130804003620p:image


まずダウンロードデータをStreamedContentとして用意します。

    private StreamedContent content;
    
    public StreamedContent getContent() {
        return content;
    }

通常はここでデータを生成して返すのですが、ここでは用意されたデータを返すだけにしておきます。


ダウンロードコンポーネントを配置します。

<p:commandButton id="dlButton" value="ダウンロード(非表示)"
            ajax="false" style="visibility: hidden">
   <p:fileDownload id="dl" value="#{downloadBean.content}"/>
</p:commandButton>

ここで、ボタンを非表示にしておきます。また、idも指定しておきます。


実際に表示されるダウンロードボタンを置きます。

<p:commandButton value="ダウンロード" ajax="true" action="#{downloadBean.prepareContent()}"
                 oncomplete="if (!args.validationFailed) document.getElementById('form:dlButton').click()"
                 update="msg"/>

ここで、actionでデータ準備のためのprepareContentメソッドを呼び出すようにします。

準備が終わったらoncompleteで実際のダウンロードボタンを押すJavaScriptを実行します。

また、ここで「args.validationFailed」によってエラーがあったときにはJavaScriptを実行しないようにしています。


prepareContentメソッドでは次のようにエラー処理を行います。

    public void prepareContent() throws IOException{
        if(!check){//いろいろチェックを行う
            String msg = "チェックを入れてください";
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_WARN, msg, msg));
            RequestContext.getCurrentInstance().addCallbackParam("validationFailed", true);
            return;
        }

今回はチェックボックスに結びつけたchekuフィールドで判定していますが、実際にはデータの存在チェックなどの処理を行います。チェックボックスのチェックがついているかどうかであれば、required属性をつければいいので今回のような処理は必要ありません。


エラーメッセージはFacesContextに追加します。

このメッセージはメッセージコンポーネントで表示させます。

<p:messages id="msg"/>

ボタンに「update="msg"」としてボタン押下後に表示を更新するようにしています。


JavaScriptでの判定で使っていたvalidationFaildパラメータは、バリデーションフェーズでエラーになったときには自動的に設定されますが、処理中では改めてRequestContextに追加する必要があります。

RequestContext.getCurrentInstance().addCallbackParam("validationFailed", true);

以上で、チェックが通らなければ処理を中断してメッセージを出すことが実現できます。


全体のソースはこちら

DownloadBean.java

package pfsample;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.imageio.ImageIO;
import org.primefaces.context.RequestContext;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;

/**
 *
 * @author naoki
 */
@ManagedBean
@ViewScoped
public class DownloadBean {
    private boolean check;
    public boolean isCheck() {
        return check;
    }
    public void setCheck(boolean check) {
        this.check = check;
    }
    
    private StreamedContent content;
    
    public StreamedContent getContent() {
        return content;
    }
    public void prepareContent() throws IOException{
        if(!check){//いろいろチェックを行う
            String msg = "チェックを入れてください";
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_WARN, msg, msg));
            RequestContext.getCurrentInstance().addCallbackParam("validationFailed", true);
            return;
        }
        
        BufferedImage bi = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = (Graphics2D) bi.getGraphics();
        g.setColor(new Color(212,228,255));
        g.fillRect(0, 0, 400, 300);
        g.setColor(Color.ORANGE);
        g.fillOval(100, 50, 200, 200);
        g.setColor(Color.BLUE);
        g.fillRect(0, 160, 400, 140);
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(bi, "png", baos);
        
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        content = new DefaultStreamedContent(bais, "image/png", "sample.png");
    }
}

sample.xml

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:p="http://primefaces.org/ui">

    <f:view contentType="text/html">
        <h:head>
            <meta content='text/html; charset=UTF-8' http-equiv="Content-Type"/>
            <title>PrimeFaces</title>
        </h:head>

        <h:body>
            <h:form id="form">
                <p:messages id="msg"/>
                <div>画像ダウンロード</div>
                <p:selectBooleanCheckbox itemLabel="ダウンロード許可"
                                         value="#{downloadBean.check}"/>
                <p:commandButton value="ダウンロード" ajax="true" action="#{downloadBean.prepareContent()}"
                                 oncomplete="if (!args.validationFailed) document.getElementById('form:dlButton').click()"
                                 update="msg"/>
                <p:commandButton id="dlButton" value="ダウンロード(非表示)"
                                 ajax="false" style="visibility: hidden">
                    <p:fileDownload id="dl" value="#{downloadBean.content}"/>
                </p:commandButton>
            </h:form>

        </h:body>

    </f:view>
</html>

2013-04-21(日) PrimeFacesのColumnGroupではconvertNumberとか使えない?

[][]PrimeFacesのColumnGroupではconvertNumberとか使えない? 03:16 PrimeFacesのColumnGroupではconvertNumberとか使えない? - きしだのはてな を含むブックマーク

PrimeFacesでは、ColumnGroupでフッターが指定できます。

PrimeFaces - DataTable - Grouping


でも、このテキストにf:convertNumberを適用させる方法がわかりません。

フォーラムでも同じ質問している人がいるのだけど「hey guys, any clue?」切ない・・・

PrimeFaces Community Forum • View topic - using convertNumber in columnGroup

2013-04-20(土) PrimeFacesのSelectOneRadioをJavaScriptから操作する

[][]PrimeFacesのSelectOneRadioをJavaScriptから操作する 08:56 PrimeFacesのSelectOneRadioをJavaScriptから操作する - きしだのはてな を含むブックマーク

kikutaro777さんが書いてるように、PrimeFacesではClient APIを使って結構手軽にJavaScriptからコンポーネントを操作できます。

PrimeFacesのTabViewをJavaScriptで操作する - Challenge Java EE !


ところが、SelectOneRadioはそういう便利APIが用意されていないので、やろうとしたときにちょっとハマります。


たとえば次のようなSelectOneRadioがあるとします。

<p:selectOneRadio id="hoge">
  <f:selectItem itemValue="L" itemLabel="左へ行く"/>
  <f:selectItem itemValue="R" itemLabel="右へ行く"/>
</p:selectOneRadio>

そうすると、それぞれのラジオボタンはhoge:0、hoge:1というIDになっているので、これをclickすることになります。


実際には、formのIDなど親コンポーネントのIDも付加されるので、実際のコードはこんな感じになります。

document.getElementById('form:hoge:0').click();

あまり実コードでこのような操作をすることはないと思うのですが、Seleniumなどで自動テストしようとすると絶対に必要になります。

そのときにハマります。というか、ハマりました。

Seleniumじゃなく、JavaFXのWebEngineつかった自作Webテストですけど。