[Tapestry 5] MultipleSelect 구현하기.

2013. 11. 7. 10:05 - 에릭투스

참고자료 : 

 1) Multiple Select 구현자료 : http://wiki.apache.org/tapestry/Tapestry5MultipleSelectOnObjects

 2) Multiple Select 소스 : http://svn.codehaus.org/chenillekit/branches/1.0/chenillekit-tapestry/src/main/java/org/chenillekit/tapestry/core/

*실제 구동하는데는 실패했으나, 참고용으로 보면 좋을 듯.

 3) Tapestry palette API : http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/corelib/components/Palette.html


Tapestry에서는 Select Multiple을 지원하는 대신, Palette라는 컴포넌트로 다중선택 UI를 지원한다.

*물론 화면으로는 Select다중선택을 할 수 있으나, 값 매핑에 있어, 하나의 값만 유지된다..


그래서, 나의 경우 다중선택 UI 지원시 Select로 해야하므로, Palette를 분석해서 구현해야했다..

(흑흑.. 그냥좀 미리 만들어주지..)


1) Palette 분석.

  : 솔직히 Palette를 보면 알겠지만, 오른쪽 Select가 선택된 Select이며, 이값이 List형태로 가져온다. 

    (내가 하고자 하는게 이거거든!!)

    

    이를 분석하면 쉽게 Select 다중선택(Multiple) 구현도 가능하다는 결론이 나온다. 

    (솔직히 쉽진 않겠지만, 그래도 따라하면 된다~!! 룰루루)

*Palette의 경우, 값이 유지되는(매핑) 과정은

1) 컴포넌트 TML에 hidden값이 있어, 선택된 항목을 JSON배열로 hidden 태그에 저장해놓는다.

2) 이후, submit되면 request.getParameter JSON배열 값을 받아, List<Object>로 하나씩 추가한다.

3) 다시 Render될때, JSON배열 값을 이용해 JS(Jquery등)으로 Selected처리하면 끝!


암튼 원리는 저렇다.


2) 실제구현.

 2-1) 커스텀 컴포넌트 구현.(Java)


package com.example.newapp.components;

import java.util.Collection;

import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.Renderable;
import org.apache.tapestry5.SelectModel;
import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ValueEncoder;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
import org.apache.tapestry5.corelib.base.AbstractField;
import org.apache.tapestry5.corelib.components.Palette;
import org.apache.tapestry5.internal.util.SelectModelRenderer;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.compatibility.DeprecationWarning;

@Import(stylesheet={"classpath:/META-INF/assets/css/select2.css","classpath:/META-INF/assets/css/select2-bootstrap.css"})
public class Select3 extends AbstractField{

    @Parameter(required = true, allowNull = false)
    private ValueEncoder encoder;
    
    @Parameter(required = true, autoconnect = true, allowNull = false)
    private Collection selected;
    
    @Parameter(required = true, allowNull = false)
    private SelectModel model;
    
    @Parameter(defaultPrefix = BindingConstants.VALIDATE)
    @SuppressWarnings("unchecked")
    private FieldValidator validate;
    
    @Inject
    @Symbol(SymbolConstants.COMPACT_JSON)
    private boolean compactJSON;
    
    public final Renderable mainRenderer = new Renderable()
    {
        public void render(MarkupWriter writer)
        {
        	System.out.println("render");
            SelectModelRenderer visitor = new SelectModelRenderer(writer, encoder);

            model.visit(visitor);
        }
    };
    
	@Override
	protected void processSubmission(String controlName) {
		System.out.println("processSubmission");
		String parameterValue = request.getParameter(controlName);
		System.out.println(controlName+" : "+parameterValue);
		
        JSONArray values = new JSONArray(parameterValue);

        // Use a couple of local variables to cut down on access via bindings

        Collection selected = this.selected;

        selected.clear();

        ValueEncoder encoder = this.encoder;

        int count = values.length();
        for (int i = 0; i <= coun-1t; i++)
        {
            String value = values.getString(i);

            Object objectValue = encoder.toValue(value);

            selected.add(objectValue);
        }

        putPropertyNameIntoBeanValidationContext("selected");
        
        //this.selected = selected;
        try
        {
            fieldValidationSupport.validate(selected, resources, validate);

            this.selected = selected;
        } catch (final ValidationException e)
        {
            validationTracker.recordError(this, e.getMessage());
        }

        removePropertyNameFromBeanValidationContext();
		
	}
	
    
    public String getInitialJSON()
    {
        JSONArray array = new JSONArray();

        for (Object o : selected)
        {
            String value = encoder.toClient(o);
            array.put(value);
        }

        return array.toString(compactJSON);
    }
    
    void beginRender()
    {
    	System.out.println("beginRender");
    	System.out.println("model : "+model);
    	System.out.println("encoder : "+encoder);
    	
        String clientId = getClientId();
        String controlName = getControlName();
        JSONObject spec = new JSONObject();
        	spec.put("clientId", clientId);
        	spec.put("controlName", controlName);
        	spec.put("selectedValue", getInitialJSON());
        javaScriptSupport.require("select3Comp").with(spec);
    }
    
    void afterRender(){
    	System.out.println("afterRender");
    }
    
    String toClient(Object value)
    {
        return encoder.toClient(value);
    }
    
    @Inject
    private DeprecationWarning deprecationWarning;

    void pageLoaded() {
        deprecationWarning.ignoredComponentParameters(resources, "select", "moveUp", "moveDown", "deselect");
    }
}

2) 컴포넌트 TML (우씨 syntax하이라이팅이 너무 구려.. 소스는 모두 참고정도만 하길 바람..ㅠ)

<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"

      xmlns:p="tapestry:parameter">

    <input type="text" name="${controlName}" value="${initialJSON}"/>

<select t:type="any" t:id="selected" id="${clientId}" multiple="multiple" style="width:200px">

<t:delegate to="mainRenderer"/>

</select>

</html>

 


여기서 중요하게 볼 부분은 선택된 값을 저장하는 Collection selected, 

submit시 실행 processSubmission 메소드(이곳에서 매핑하는 코딩을 짜주면 된다)이다.


위에서 설명한대로, submit하게되면 processSubmission 메소드가 실행되고,

여기서 request.getParameter를 통해 JSON배열값을 가져오며, 

이를 풀면서 selected(List)에 추가한다. (안타깞게도 validation 기능은 안된다.ㅠ 에러남)


이후 컴포넌트 화면에 Render(나타나게)되면 beginRender 메소드가 실행되는데, 

여기서 미리 select2라이브러리를 적용한 "select3Comp"를 requirejs를 이용해 가져온다.



@Parameter(required = true, autoconnect = true, allowNull = false)
    private Collection selected;

@Override
	protected void processSubmission(String controlName) {
		System.out.println("processSubmission");
		String parameterValue = request.getParameter(controlName);
		System.out.println(controlName+" : "+parameterValue);
		
        JSONArray values = new JSONArray(parameterValue);

        // Use a couple of local variables to cut down on access via bindings

        Collection selected = this.selected;

        selected.clear();

        ValueEncoder encoder = this.encoder;

        int count = values.length();
        for (int i = 0; i <= count-1; i++)
        {
            String value = values.getString(i);

            Object objectValue = encoder.toValue(value);

            selected.add(objectValue);
        }

        putPropertyNameIntoBeanValidationContext("selected");
        
        this.selected = selected;
        /*try
        {
            fieldValidationSupport.validate(selected, resources, validate);

            this.selected = selected;
        } catch (final ValidationException e)
        {
            validationTracker.recordError(this, e.getMessage());
        }*/

        removePropertyNameFromBeanValidationContext();
		
	}

    void beginRender()
    {
    	System.out.println("beginRender");
    	System.out.println("model : "+model);
    	System.out.println("encoder : "+encoder);
    	
        String clientId = getClientId();
        String controlName = getControlName();
        JSONObject spec = new JSONObject();
        	spec.put("clientId", clientId);
        	spec.put("controlName", controlName);
        	spec.put("selectedValue", getInitialJSON());
        javaScriptSupport.require("select3Comp").with(spec);
    }


자바스크립트 컴포넌트의 역할은 단순하다, 

자바에서 넘겨준 JSON을 이용해 Select에 선택된 값을 selected해주고,

<select>가 변경될 때마다 hidden값에 JSON배열 형태로 저장만 해주면 된다.!! 


끝~~!!!


define(["jquery","select2"],function($,select2){
	return function(spec){
		var controlName = spec.controlName;
		var id = spec.clientId;
		var _selectedValue = eval(spec.selectedValue);
		
		var _hidden = $("input[name="+controlName+"]");
		var _multipleSelector = $("#"+id);
		
		_multipleSelector.on('change',function(){
			var selectedValue = $(this).val();
			_hidden.val("["+selectedValue+"]");
		});
		
		_multipleSelector.val(_selectedValue);
		_multipleSelector.select2();
	};
});


결국, 왼쪽에 보면, JSONArray를 저장하고있는 hidden(잘보이기위해 text로 바꿈 ㅋ)

<Select>는 select2 라이브러리를 이용해 적용한 화면이다.



콘솔에서 보이는 것처럼, Collection selected에 선택했던 항목 값들이 들어가는 것을 확인할 수 있다.




다른 카테고리의 글 목록

Workspace/Web Dev 카테고리의 포스트를 톺아봅니다