인프런 커뮤니티 질문&답변

이상진님의 프로필 이미지
이상진

작성한 질문수

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술

체크 박스 - 멀티

[체크박스-멀티] 안녕하세요. #ids의 작동 방식에 대한 질문입니다.

해결된 질문

작성

·

403

·

수정됨

2

[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? 예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예

[질문 내용]

안녕하세요, 우선 좋은 강의에 감사드립니다.

다름이 아니라, 강의를 듣는 도중 멀티 체크박스를 정의하는 과정에서 #ids.prev('regions')부분에서 이해가 되지 않았습니다. th:each를 사용하는 경우 id에 1, 2, 3과 같은 숫자를 붙여 regions1, regions2.. 와 같이 만드는데, 왜 #ids.prev에선 'regions'를 입력으로 넣는지가 이해가 되지 않았습니다.

(뿐만 아니라 전체 흐름을 조금 더 명확하게 이해하고 싶었습니다.)

구글링을 해봤는데, 해당 강의에 대한 블로그 정리는 많지만 제가 원하는 내용은 찾지를 못했습니다..ㅜㅜ

그래서, 이번 기회에 소스코드를 찾아보며 이해를 해보려고 했는데, 아래 작성된 과정이 맞는지, 제가 잘 이해한게 맞는지가 궁금하여 질문 드리게 되었습니다. 너무나도 긴 질문이라 미리 죄송하다는 말씀 드립니다..

  1. 다음 코드는 SpringInputCheckboxFieldTagProcessor 클래스의 doProcess()와, AbstractSpringFIeldTagProcess 클래스의 computeId() 메서드 입니다.

protected void doProcess(ITemplateContext context, IProcessableElementTag tag, AttributeName attributeName, String attributeValue, IThymeleafBindStatus bindStatus, IElementTagStructureHandler structureHandler) {
  String name = bindStatus.getExpression();
  name = name == null ? "" : name;
  String id = this.computeId(context, tag, name, true);
	
	// 이하 생략..
}
protected final String computeId(ITemplateContext context, IProcessableElementTag tag, String name, boolean sequence) {
    // 이전 부분 생략..
    if (sequence) {
        Integer count = context.getIdentifierSequences().getAndIncrementIDSeq(id);
        return id + count.toString();
    }
}

computeId()를 보면, sequence인 경우(→ 이 경우는 th:each가 들어간 경우라고 추측합니다.) Id에 count를 붙여서 반환하도록 되어있습니다.

2.IdentifierSequences 클래스

public final class IdentifierSequences {
  private final Map<String, Integer> idCounts = new HashMap(1, 1.0F);

  public IdentifierSequences() {
  }

  public Integer getAndIncrementIDSeq(String id) {
      Validate.notNull(id, "ID cannot be null");
      Integer count = (Integer)this.idCounts.get(id);
      if (count == null) {
          count = 1;
      }

      this.idCounts.put(id, count + 1);
      return count;
  }
	
	public Integer getPreviousIDSeq(String id) {
      Validate.notNull(id, "ID cannot be null");
      Integer count = (Integer)this.idCounts.get(id);
      if (count == null) {
          throw new TemplateProcessingException("Cannot obtain previous ID count for ID \\"" + id + "\\"");
      } else {
          return count - 1;
      }
  }
}

IdentifierSequences 클래스를 보면, idCounts 라는 Map에 id와 count 정보를 저장하고, 1에서의 computeId()getAndIncrementIDSeq() 를 통해 count 정보를 얻어냅니다.

이 세부 과정을 생각해보면,

  1. 처음에 th:field=”*{regions}”를 통해 regions라는 id가 들어가면 IdentifierSequences 클래스의 Map<String, Integer> idCounts 에는 regions라는 id가 없어 getAndIncrementIDSeq() 의 count값은 1이 됩니다.

  2. this.idCount.put(id, count + 1); 을 통해 idCounts에는 {”regions” : 2}가 저장되며, getAndIncrementIDSeq()가 반환하는 count값은 1이 됩니다.

  3. 즉, 처음에 regions라는 id가 들어가면, 이는 <input> 태그에는 regions1이라는 id로 지정되지만 IdentifierSequence의 idCount 맵에는 count가 2로 되어있는 상태입니다.

  4. IDs 클래스

public class Ids {
  private final ITemplateContext context;

  public String prev(Object id) {
      Validate.notNull(id, "ID cannot be null");
      String str = id.toString();
      return str + this.context.getIdentifierSequences().getPreviousIDSeq(str);
  }

	public Ids(ITemplateContext context) {
      Validate.notNull(context, "Context cannot be null");
      this.context = context;
  }
}

그러면 #ids.prev(’regions’) 를 통해 label이 regions1로 등록되는 과정은 다음과 같이 이해할 수 있을 것 같습니다.

  1. prev 메서드는 id를 입력받아, IdentifierSequencegetPreviousIDSeq()을 호출하여 얻은 숫자를 뒤에 붙여 label의 id를 지정합니다.

  2. 이전 문단의 IdentifierSequencegetPreviousIDSeq() 코드를 보면, idCount에서 얻은 count에 1을 빼서 반환합니다.

  3. idCount에는 count가 2로 지정되어 있기에, 최종적으로 #ids.prev(’regions’)의 결과는 regions1이 됩니다.


    활용 : #ids.next의 사용

<label th:for="${#ids.next('regions')}"
       th:text="${region.value}" class="form-check-label">서울</label>
<input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input">

label과 input 태그의 위치를 바꾸고 #ids.next를 사용하면, 이전과 같이 label의 for와 input의 id를 일치시킬 수 있습니다. 결과는 다음과 같습니다.

<!-- multi checkbox -->
<div>
    <div>등록 지역</div>
    <div class="form-check form-check-inline">
        <label for="regions1"
               class="form-check-label">서울</label>
        <input type="checkbox" value="SEOUL" class="form-check-input" id="regions1" name="regions"><input type="hidden" name="_regions" value="on"/>
    </div>
    <div class="form-check form-check-inline">
        <label for="regions2"
               class="form-check-label">부산</label>
        <input type="checkbox" value="BUSAN" class="form-check-input" id="regions2" name="regions"><input type="hidden" name="_regions" value="on"/>
    </div>
    <div class="form-check form-check-inline">
        <label for="regions3"
               class="form-check-label">제주</label>
        <input type="checkbox" value="JEJU" class="form-check-input" id="regions3" name="regions"><input type="hidden" name="_regions" value="on"/>
    </div>
</div>


세부 과정을 보면 다음과 같습니다.

public String next(Object id) {
    Validate.notNull(id, "ID cannot be null");
    String str = id.toString();
    return str + this.context.getIdentifierSequences().getNextIDSeq(str);
}

next()는 IdentifierSequencesgetNextIDSeq() 를 통해 count 정보를 얻습니다.

public Integer getNextIDSeq(String id) {
    Validate.notNull(id, "ID cannot be null");
    Integer count = (Integer)this.idCounts.get(id);
    if (count == null) {
        count = 1;
    }

    return count;
}

getNextIDSeq() 를 보면, idCounts에 id가 없는 경우 1을 반환합니다. 즉, 처음에 #ids.next(’regions’)가 입력되면, getNextIDSeq()는 1을 반환하기에 첫 번째 label은 for=’regions1’ 이 됩니다.

다음 과정은 2번 문단에 적은 세부 과정과 동일한데, 이전의 #ids.prev에서와 달리 #ids.next는 count에 1을 뺀 값이 아닌 count 그대로를 반환하므로, #ids.prev를 사용할 때와 같이 for와 id가 일치된다고 생각할 수 있습니다.


읽어주셔서 감사드립니다. 새해 복 많이 받으세요!(질문 작성일이 설 당일이라.. 겸사겸사 인사드립니다!)

답변 1

2

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 이상진님

명절에도 열심히 공부하시는 군요 🙂 새해 복 많이 받으세요.

생각하신 것 처럼 label이 먼저 나오는 경우 이 부분을 고민하지 않아도 됩니다. 다만 input이 먼저 나오는 경우에는 이전 카운트로 사용되는 regions의 prev 값을 사용해야 합니다. 왜냐하면 label과 input을 맞추어야 하는데 이미 카운트가 하나 증가해 버렸으니까요.

참고로 다음과 같이 input을 두 번 넣어보고, 실행 결과에서 증가한 카운터를 보면 작동 방식이 더 이해가 되실거에요.

 

<div th:each="region : ${regions}" class="form-check form-check-inline">

<input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input">

<input type="checkbox" th:field="*{regions}" th:value="${region.key}" class="form-check-input">

<label th:for="${#ids.prev('regions')}"

th:text="${region.value}" class="form-check-label">서울</label>

</div>

 

실행 결과

        <!-- multi checkbox -->
        <div>
            <div>등록 지역</div>
            <div class="form-check form-check-inline">
                <input type="checkbox" value="SEOUL" class="form-check-input" id="regions1" name="regions"><input type="hidden" name="_regions" value="on"/>
                <input type="checkbox" value="SEOUL" class="form-check-input" id="regions2" name="regions"><input type="hidden" name="_regions" value="on"/>
                <label for="regions2"
                       class="form-check-label">서울</label>
            </div>
            <div class="form-check form-check-inline">
                <input type="checkbox" value="BUSAN" class="form-check-input" id="regions3" name="regions"><input type="hidden" name="_regions" value="on"/>
                <input type="checkbox" value="BUSAN" class="form-check-input" id="regions4" name="regions"><input type="hidden" name="_regions" value="on"/>
                <label for="regions4"
                       class="form-check-label">부산</label>
            </div>
            <div class="form-check form-check-inline">
                <input type="checkbox" value="JEJU" class="form-check-input" id="regions5" name="regions"><input type="hidden" name="_regions" value="on"/>
                <input type="checkbox" value="JEJU" class="form-check-input" id="regions6" name="regions"><input type="hidden" name="_regions" value="on"/>
                <label for="regions6"
                       class="form-check-label">제주</label>
            </div>
        </div>

참고로 우리가 학습을 할 때 깊이 파야 하는 부분이 있고, 때로는 너무 파기 보다는 넘어가도 좋은 부분들이 있는데요. 이런 부분은 타임리프가 제공하는 기능을 단순히 사용하는 것이기 때문에 이런 부분들은 너무 깊이있게 파기 보다는 기능의 사용법 정도만 이해하면 충분하다 생각합니다 🙂

추가로 더 필요한 내용 관련해서는 다음 링크를 참고해주세요.

https://www.inflearn.com/questions/1043247

https://ktaes.tistory.com/102

감사합니다.

이상진님의 프로필 이미지
이상진
질문자

영한님 안녕하세요, 우선 연휴임에도 직접 답변 달아주셔서 감사드립니다!

원리를 한번 알고 나니, 예시로 달아주신 input을 두 번 넣는 경우도 머리속에 바로 그려지긴 하지만, 말씀하신 것 처럼 너무 다 이해하려고 하는 것이 아닌가.. 라는 생각도 많이 들었습니다.
(뭔가 집요하게 파는 성격을 내려놓고 싶은데, 이게 참 쉽지 않네요 ㅎㅎ..)

그래도 추가로 남겨주신 링크들까지 참고하여, 이 부분에 대해서는 정말 확실하게 이해할 수 있게 되었습니다. 정말 긴 질문임에도 친절하게 답변해주셔서 감사드리며, 앞으로는 조금 더 가벼운 마음으로 학습할 수 있도록 노력해 보겠습니다!!

감사합니다!

이상진님의 프로필 이미지
이상진

작성한 질문수

질문하기