2014년 3월 10일 월요일

Meteor/handlebars에서 radio/select 다루기

자주쓰지만 은근 귀찮은 것이 radio와 select다.
특히 최초에 렌더링 할때 선택하게 하는게 번거롭다.
#each로 option이나 input에 들어갈 별도의 키를 find의 transform에서 만들거나 아예 fetch를 하거나 array를 조작하거나 하는 건 어렵지 않지만 매번 같은 상황일때마다 코딩을 해야되니까 안 이쁘쟈나. (그렇쟈나 손구락 뿌러지쟈나 카피엔 페이스트 귀찮쟈나 짜증나쟈나!)

물론 checkbox도 귀찮지만 비슷하게 응용하면 될 것이니
handlebars의 block helpers를 사용하여 이 귀찮음을 해결해볼 수 없을까 해서 코드를 만들어보았다.

먼저 select를 해보면 다음과 같은 그림인데

<select>
  {{#each array}}
  <option value="{{code}}">{{name}}</option>
  {{/each}}
</select>

array중 code가 특정값과 일치하면 selected="selected"가 되도록하려면 간단하게는 option이라는 Helper를 만들어 {{option name code value}}식으로 할 수도 있겠지만
<option> 이라는 객체가 없어지니까 별로 좋지 않다.
그렇다면 특정 블록을 감싸안아서 인자로 받은 값과 같은 option이 있는지 찾아서 수정한 후 대치하면 될 것이다라고 생각.

Handlebars.registerHelper "select", (value, options) ->
select = document.createElement("div")
$(select).html options()
$("option", select).filter("[value='#{value}']").attr "selected", "selected"
select.innerHTML
이런 식으로 구현해보았다.
registerHelper는 마지막 인자 뒤에 옵션이 붙을 경우 해당 템플릿을 렌더링하는 함수를 가르킨다.
options()로 select block helpers 안의 html을 가져온 뒤 DOM을 생성하고
filter로 값을 찾은 후 attr로 조작해보았다.

<select>
  {{#select matchValue}}
  {{#each array}}
  <option value="{{code}}">{{name}}</option>
  {{/each}}
  {{/select}}
</select>

matchValue와 array를 넣고 실행해보니 잘 된다. 
같은 방식으로 radio 에도 적용하려고 보니 한 가지 이상한 점이 있다.
<$data:aflkj21j2423> </$data> 형태의 태그가 붙는 것이다.
<option>이야 <select>안쪽에 있어서 상관없지만 <input type="radio"/>같은 경우엔 문제가 된다.

아마 기존 handlebars를 자체는 상관없겠지만 Meteor는 each문에서 특정 object가 갱신이 될때 어떤 걸 다시 만들어야하는지 알기 위해 임의의 인덱스를 필요로 한다.
하지만, 우리는 option이나 radio를 렌더링 하는데 문제가 되므로 정규식을 써서 제거한다.
혹시 이것도 관련있는 사이드 이펙트나 더 좋은 아이디어가 있으면 Meteor_KR(https://plus.google.com/communities/113287389139180020424)에 제보 바란다.

그리하여 최종 작성한 코드는 다음과 같다.
Handlebars.registerHelper "select", (value, options) ->
select = document.createElement("div")
$(select).html options().replace(/<[/]*\$data:\w+>/g, "")
$("input[type=radio]", select).filter("[value='#{value}']").attr "checked", "checked"
$("option", select).filter("[value='#{value}']").attr "selected", "selected"
select.innerHTML
/* js 버전 */
Handlebars.registerHelper("select", function(value, options) {
var html, select;
select = document.createElement("div");
$(select).html(options().replace(/<[/]*\$data:\w+>/g, ""));
$("input[type=radio]", select).filter("[value='" + value + "']").attr('checked', 'checked');
$("option", select).filter("[value='" + value + "']").attr('selected', 'selected');
return select.innerHTML;
});
gist(https://gist.github.com/acidsound/a7de8f12acafc787dff2)에 공유하니 요긴하게 사용하시길.
테스트 해본 결과 리엑티브 객체에서도 잘 동작한다.
체크박스나 다른 커스텀 컨트롤러도 비슷하게 해볼 수 있다.

핸들바의 헬퍼는 참 알아야할 것이 적은 것에 비해 자유도가 높고 강력하다.