본문 바로가기

퍼블리싱/HTML | CSS | Javascript

tree menu with checkbox

플러그인을 쓰지 않고 구현하는 tree menu

 

See the Pen tree menu with custom checkbox by publisher.kim (@publisherkim) on CodePen.

 

 

 

html

<!-- 첫번째 treebox -->
<div class="treebox">
  <!-- first depth -->
  <ul class="fdepth">
    <li>
      <button class="btn_tree" title="트리열기"></button>
      <span class="chk">
        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01"><label for="cbx01">전체</label></span>
      </span>
      <!-- second depth -->
      <ul class="sdepth">
        <li>
          <button class="btn_tree" title="트리열기"></button>
          <span class="chk">
            <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1"><label for="cbx01_1">메뉴1</label></span>
          </span>
          <!-- third depth -->
          <ul class="tdepth">
            <li>
              <button class="btn_tree" title="트리열기"></button>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_1"><label for="cbx01_1_1">메뉴1_1</label></span>
              </span>
              <!-- fourth depth -->
              <ul class="fodepth">
                <li>
                  <button class="btn_tree" title="트리열기"></button>
                  <span class="chk">
                    <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_1_1"><label for="cbx01_1_1_1">메뉴1_1_1</label></span>
                  </span>
                  <!-- fifth depth -->
                  <ul class="fidepth">
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_1_1_1"><label for="cbx01_1_1_1_1">메뉴1_1_1_1</label></span>
                      </span>
                    </li>
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_1_1_2"><label for="cbx01_1_1_1_2">메뉴1_1_1_2</label></span>
                      </span>
                    </li>
                  </ul>
                </li>
              </ul>
            </li>
            <li>
              <button class="btn_tree" title="트리열기"></button>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2"><label for="cbx01_1_2">메뉴1_2</label></span>
              </span>
              <!-- fourth depth -->
              <ul class="fodepth">
                <li>
                  <button class="btn_tree" title="트리열기"></button>
                  <span class="chk">
                    <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_1"><label for="cbx01_1_2_1">메뉴1_2_1</label></span>
                  </span>
                  <!-- fifth depth -->
                  <ul class="fidepth">
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_1_1"><label for="cbx01_1_2_1_1">메뉴1_2_1_1</label></span>
                      </span>
                    </li>
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_1_2"><label for="cbx01_1_2_1_2">메뉴1_2_1_2</label></span>
                      </span>
                    </li>
                  </ul>
                </li>
                <li>
                  <button class="btn_tree" title="트리열기"></button>
                  <span class="chk">
                    <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_2"><label for="cbx01_1_2_2">메뉴1_2_2</label></span>
                  </span>
                  <!-- fifth depth -->
                  <ul class="fidepth">
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_2_1"><label for="cbx01_1_2_2_1">메뉴1_2_2_1</label></span>
                      </span>
                    </li>
                    <li>
                      <span class="chk">
                        <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_1_2_2_2"><label for="cbx01_1_2_2_2">메뉴1_2_2_2</label></span>
                      </span>
                    </li>
                  </ul>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <button class="btn_tree" title="트리열기"></button>
          <span class="chk">
            <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_2"><label for="cbx01_2">메뉴2</label></span>
          </span>
          <!-- third depth -->
          <ul class="tdepth">
            <li>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_2_1"><label for="cbx01_2_1">메뉴2_1</label></span>
              </span>
            </li>
            <li>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_2_2"><label for="cbx01_2_2">메뉴2_2</label></span>
              </span>
            </li>
            <li>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx01" id="cbx01_2_3"><label for="cbx01_2_3">메뉴2_3</label></span>
              </span>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

<!-- 두번째 treebox -->
<div class="treebox">
  <!-- first depth -->
  <ul class="fdepth">
    <li>
      <button class="btn_tree" title="트리열기"></button>
      <span class="chk">
        <span class="cbx"><input type="checkbox" name="cbx02" id="cbx02"><label for="cbx02">전국</label></span>
      </span>
      <!-- second depth -->
      <ul class="sdepth">
        <li>
          <button class="btn_tree" title="트리열기"></button>
          <span class="chk">
            <span class="cbx"><input type="checkbox" name="cbx02" id="cbx02_1"><label for="cbx02_1">서울</label></span>
          </span>
          <!-- third depth -->
          <ul class="tdepth">
            <li>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx02" id="cbx02_1_1"><label for="cbx02_1_1">종로구</label></span>
              </span>
            </li>
            <li>
              <span class="chk">
                <span class="cbx"><input type="checkbox" name="cbx02" id="cbx02_1_2"><label for="cbx02_1_2">강남구</label></span>
              </span>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

<!-- 단일 chekbox 있는 treebox -->
<div class="treebox">
  <ul class="fdepth"><!-- first depth -->
    <li>
      <span class="chk">
        <span class="cbx"><input type="checkbox" name="cbx03" id="cbx03_1"><label for="cbx03_1">2022년</label></span>
      </span>
    </li>
    <li>
      <span class="chk">
        <span class="cbx"><input type="checkbox" name="cbx03" id="cbx03_2"><label for="cbx03_2">2021년</label></span>
      </span>
    </li>
  </ul>
</div>

 

css

/* treebox */
.treebox{border:1px solid #ddd; padding:20px; margin-left:20px; margin-top: 20px; max-width: 400px; box-sizing: border-box;} 
.treebox .btn_tree{display: inline-block; width: 16px; height: 16px; border: 0; background: url(https://umings.github.io/images/i_tree_off.png) no-repeat center; vertical-align: -1px; margin-right: 5px; cursor: pointer;}
.treebox .btn_tree.on{background: url(https://umings.github.io/images/i_tree_on.png) no-repeat center; }
.treebox li .chk{padding: 3px 0; margin-left: 16px;}
.treebox li .btn_tree + .chk{margin-left: 0;}
.treebox .fdepth > li > .chk{margin-left: 0;}
.treebox .sdepth li{background: url(https://umings.github.io/images/i_line2.png) no-repeat left 8px top -9px, url(https://umings.github.io/images/i_line3.png) repeat-y left 8px top; padding-left: 16px;}
.treebox .sdepth li:last-of-type{background: url(https://umings.github.io/images/i_line.png) no-repeat left 8px top -8px;}
.treebox ul{display: none;}
.treebox .fdepth{display: block;}

  /* custom checkbox*/
  .treebox span.chk {display: inline-block;}
  .treebox .chk input {opacity: 0;	position: absolute;}
  .treebox .chk > span {display: inline-block;}
  .treebox .chk > span label {display: inline-block;position: relative;line-height: 1;cursor: pointer; padding-left:1.2em; }
  .treebox .chk .cbx label::before {content: '';position: absolute;left: 0;top: 1px;width:15px;height:15px;border: 1px solid #c6c6c6;background: #fff;border-radius: 3px;margin-right: 0;vertical-align: bottom;cursor:pointer;}
  .treebox .chk .cbx input:checked + label::before {background: #0c76ce; border: 1px solid #0c76ce;}
  .treebox .chk .cbx input:checked + label::after{content:'';position:absolute;left: 5px;top: 3px;width:5px;height:8px;border-width:0 2px 2px 0;border-style:solid;border-color:#fff;transform:rotate(45deg);}

 

js *jquery 필요

$(function(){
  // 트리 열고닫기
    $(".treebox .btn_tree").click(function(){
    if($(this).hasClass("on")){
      $(this).removeClass("on");
      $(this).attr("title","트리 열기")
      $(this).parent().children("ul").hide();
    }else{
      $(this).addClass("on");
      $(this).attr("title","트리 닫기")
      $(this).parent().children("ul").show();
    }
    return false;
  }) 

  // parents() selector로 잡히는 모든 상위 요소를 반환한다.
  // closest() selector로 잡히는 상위 요소중 가장 근접한 하나를 반환한다.
  $(".treebox .chk input").click(function(){

    var checked = $(this).is(':checked');	// 체크 박스 클릭 후 상태
    var checkId = $(this).attr("id");		// 체크 박스 아이디

    // 1. 체크 후 처리
    if(checked) {
      
      // 하위 체크박스 전체 체크
      $(this).parent().parent().next().find("[type='checkbox']").prop('checked', true);

      var parentTrue = function(id) {
        var lastIndex = id.lastIndexOf("_");		// 마지막 '_' 언더바가 있는 인덱스 값 호출
        var parentId = id.substring(0, lastIndex);	// 파라미터로 받아온 아이디에서 마지막 '_' 언더바까지 자른 아이디
        var emptyList = [];							// 체크를 위한 임시 배열
        var emptyNum = 1;							// 체크를 위한 첫번째 이름
        var elemId = "#"+parentId+"_"+emptyNum;		// 체크박스 아이디 초기화 1번부터 시작

        // 부모 체크박스 아이디 체크 (재귀 함수를 사용하기에 마지막에는 이름이 공백으로 나온다 / 스크립트 에러 방지)
        if(parentId != '') {
          // 아이디값에 1씩 추가하여 실제 태그가 있을때 까지 emptyList에 추가
          // #cbx01_1 , #cbx01_2 , #cbx01_3
          while($(elemId).length) {
            emptyList.push(elemId);
            emptyNum++;								// 1씩 추가
            elemId = "#"+parentId+"_"+emptyNum;		// 아이디 추출
          }
          // 재귀함수 호출 구분 값
          var has = true;
          // 배열안에 체크박스 아이디를 불러와 한개라도 체크되지 않는다면 그대로 종료
          emptyList.forEach(function(id) {
            if(!$(id).is(":checked")) {
              has = false;
              return false;
            }
          });
          // 체크박스가 모두 체크되어 있다면 부모 체크박스 체크 후 재귀함수 호출
          if(has) {
            $("#"+parentId).prop("checked", true);
            parentTrue(parentId);
          }
        }

      }
      parentTrue(checkId);
    }


    // 2. 체크 해제 후 처리
    if(!checked) {
      // 하위 체크박스 전체 해제
      $(this).parent().parent().next().find("[type='checkbox']").prop('checked', false);

      var parentFalse = function(id) {
        var lastIndex = id.lastIndexOf("_");			// '_' 마지막 언다바를 기준으로 인덱스 호출
        var parentId = id.substring(0, lastIndex);		// 인덱스 까지 문자 호출
        if(parentId != '' && $("#" + parentId).length) {
          $("#" + parentId).prop('checked', false);	// 상위 부모 체크박스 비활성화
          parentFalse(parentId);						// 재귀함수 - 메소드 재호출
        }
      }
      parentFalse(checkId);
    }
  });
})

 

728x90