스케치북 게시판 스킨 분류를 드릴다운 스타일(단계별 하위분류) 추가
이 Tip을 작성후 분류를 대한만국 행정동을 기본으로 가져오는 방법을 다시 Tip으로 남깁니다.
1. 대한민국 시도 → 시군구 → 읍면동 3단계를 게시판의 분류(카테고리)로 일괄 생성합니다. (단 리 단계는 만들지 않음)
압축 파일중에서 mh_seed_korea_categories_web.php 파일에서 module_srl 값을 수정합니다.
44줄 const MH_MODULE_SRL = 55; 이부분을 전국 행정동을 분류로 생성할 게시판 module_srl=xx 값을 찾아서 수정합니다.
2. 수정한 파일을 modules/board/skins/sketchbook5_MH 폴더에 업로드 합니다.
3. 브라우즈 주소창에 아래 부분을 복사(단 도메인 부분은 자신의 주소) 해서 실행합니다.
https://도메인/modules/board/skins/sketchbook5_MH/mh_seed_korea_categories_web.php?auto=Y
설치하는데 시간이 꽤 소요됩니다.
4. 설치 후 해당 모듈 사이트 설정에 접속하면 분류가 자동으로 행정동으로 출력됩니다.(자료가 길어서 출력되는데 시간이 걸림)

5. mh_category_cascade.js 파일과 mh_category_drill.js 파일은 /sketchbook5_MH/js 폴더에 업로드 합니다.
mh_facet_intersect.php 파일과 다른 파일들은 /sketchbook5_MH 폴더에 업로드 합니다.
아래 파일 수정 부분(6~8번)은 참고 삼아서 작성한거라 파일 업로드 후에는 참고만 하시고
혹시나 다른 게시판에 적용을 할려면 참고하세요.
6. skin.xml 수정
<var name="cnb" type="radio">
<title xml:lang="ko">카테고리 메뉴 스타일</title>
<title xml:lang="en">Category Menu Style</title>
<description xml:lang="ko">'표시 안함' 게시판 설정의 '게시판 정보' 메뉴의 '분류 사용' 항목과 관계없이 표시하지 않습니다. '박스 스타일'은 2차 카테고리를 지원하지 않습니다.</description>
<description xml:lang="en">'Hide' will not display regardless of the 'Use Category' item in the 'Board Info' menu of board settings. 'Box style' does not support secondary categories.</description>
<options value="">
<title xml:lang="ko">그라데이션 스타일(기본)</title>
<title xml:lang="en">Gradation Style (Default)</title>
</options>
<options value="cTab">
<title xml:lang="ko">탭 스타일</title>
<title xml:lang="en">Tab Style</title>
</options>
<options value="cnb3">
<title xml:lang="ko">박스 스타일</title>
<title xml:lang="en">Box Style</title>
</options>
<options value="cnb4">
<title xml:lang="ko">박스+테두리 스타일</title>
<title xml:lang="en">Box+Border Style</title>
<options value="cDrill">
<title xml:lang="ko">드릴다운 스타일(단계별 하위분류)</title>
<title xml:lang="en">Drilldown Style (Step-by-step Subcategory)</title>
</options>
<options value="N">
<title xml:lang="ko">표시 안함</title>
<title xml:lang="en">Hide</title>
</options>
</var>
~ 생략
<var name="top_tag_btn" type="radio">
<title xml:lang="ko">상단 TAG 버튼</title>
<title xml:lang="en">Top TAG Button</title>
<description xml:lang="ko">TAG 버튼 표시 선택시 게시판 상단에 Tag 분류 버튼 출력</description>
<description xml:lang="en">When TAG button display is selected, Tag category buttons are displayed at the top of the board.</description>
<options value="">
<title xml:lang="ko">표시 안함(기본)</title>
<title xml:lang="en">Hide (Default)</title>
</options>
<options value="Y">
<title xml:lang="ko">표시</title>
<title xml:lang="en">Display</title>
</options>
</var>
<var name="tag_cate1n" type="text">
<title xml:lang="ko"> └ 분류1 확장변수 ID</title>
<title xml:lang="en"> └ Category 1 Tag Names</title>
<description xml:lang="ko">분류1 출력될 확장변수 ID(기본 sports)</description>
<description xml:lang="en">Tag names to be displayed in Category 1</description>
</var>
<var name="tag_cate1" type="text">
<title xml:lang="ko"> └ 분류1 출력 이름</title>
<title xml:lang="en"> └ Category 1 Display Name</title>
<description xml:lang="ko">TAG 버튼 표시 선택시 게시판 상단에 Tag 분류 버튼 출력중 분류1 출력 이름</description>
<description xml:lang="en">When TAG button display is selected, the display name of Category 1 among Tag category buttons at the top of the board</description>
</var>
<var name="tag_cate2n" type="text">
<title xml:lang="ko"> └ 분류2 확장변수 ID(기본 change)</title>
<title xml:lang="en"> └ Category 2 Tag Names</title>
<description xml:lang="ko">분류2 출력될 확장변수 ID</description>
<description xml:lang="en">Tag names to be displayed in Category 2</description>
</var>
<var name="tag_cate2" type="text">
<title xml:lang="ko"> └ 분류2 출력 이름</title>
<title xml:lang="en"> └ Category 2 Display Name</title>
<description xml:lang="ko">TAG 버튼 표시 선택시 게시판 상단에 Tag 분류 버튼 출력중 분류2 출력버튼 이름</description>
<description xml:lang="en">When TAG button display is selected, the display name of Category 2 button among Tag category buttons at the top of the board</description>
</var>
<var name="cnb_tag_order" type="radio">
<title xml:lang="ko">카테고리 / 상단 TAG버튼 출력 순서</title>
<title xml:lang="en">Category / Top TAG Button Order</title>
<description xml:lang="ko">카테고리 메뉴와 상단 TAG 버튼 중 어느 것을 먼저 출력할지 선택합니다.</description>
<description xml:lang="en">Choose which one is displayed first: the category menu or the top TAG button.</description>
<options value="">
<title xml:lang="ko">카테고리 먼저(기본)</title>
<title xml:lang="en">Category First (Default)</title>
</options>
<options value="tag_first">
<title xml:lang="ko">TAG버튼 먼저</title>
<title xml:lang="en">TAG Button First</title>
</options>
</var>
7. list.html 수정
<include target="__setting.html" />
{@
$mh_c1v = (string)Context::get('mh_c1v');
$mh_c2v = (string)Context::get('mh_c2v');
if($mh_c1v || $mh_c2v):
$mh_eid1 = $mi->tag_cate1n;
$mh_eid2 = $mi->tag_cate2n;
$oDB = DB::getInstance();
$mh_filter_srls = null;
if($mh_c1v && $mh_eid1):
$mh_stmt1 = $oDB->query(
'SELECT document_srl FROM document_extra_vars WHERE module_srl = ? AND eid = ? AND value = ?',
(int)$mi->module_srl, $mh_eid1, $mh_c1v
);
$mh_set1 = array();
foreach($mh_stmt1->fetchAll() as $mh_row1):
$mh_row1 = is_array($mh_row1) ? (object)$mh_row1 : $mh_row1;
$mh_set1[] = (int)$mh_row1->document_srl;
endforeach;
$mh_filter_srls = $mh_set1;
endif;
if($mh_c2v && $mh_eid2):
$mh_stmt2 = $oDB->query(
'SELECT document_srl FROM document_extra_vars WHERE module_srl = ? AND eid = ? AND value = ?',
(int)$mi->module_srl, $mh_eid2, $mh_c2v
);
$mh_set2 = array();
foreach($mh_stmt2->fetchAll() as $mh_row2):
$mh_row2 = is_array($mh_row2) ? (object)$mh_row2 : $mh_row2;
$mh_set2[] = (int)$mh_row2->document_srl;
endforeach;
$mh_filter_srls = ($mh_filter_srls === null) ? $mh_set2 : array_values(array_intersect($mh_filter_srls, $mh_set2));
endif;
if($mh_filter_srls !== null):
$mh_filter_lookup = array_flip($mh_filter_srls);
foreach((array)$document_list as $mh_dk=>$mh_dv):
$mh_dsrl = is_object($mh_dv) ? $mh_dv->get('document_srl') : $mh_dv['document_srl'];
if(!isset($mh_filter_lookup[(int)$mh_dsrl])):
unset($document_list[$mh_dk]);
endif;
endforeach;
$total_count = count($document_list);
endif;
endif;
}
~ 생략
<!--// 카테고리 / 상단 TAG버튼 순서 (설정: $mi->cnb_tag_order) -->
{@
$mh_cnb_order = ($mi->cnb_tag_order=='tag_first') ? 2 : 1;
$mh_tag_order = ($mi->cnb_tag_order=='tag_first') ? 1 : 2;
$mh_cnb_style = 'order:'.$mh_cnb_order.';margin-top:0;margin-bottom:'.($mi->default_style=='blog' ? '30px' : '0').';';
}
<div class="cnb_n_list">
<div class="mh_cnb_tag_order" style="display:flex;flex-direction:column;gap:10px">
<style>
.mh_cnb_tag_order .bd_cnb,
.mh_cnb_tag_order ul.cTab,
.mh_cnb_tag_order ul.cnb3,
.mh_cnb_tag_order .mh_cnb_drill { margin-bottom:0 !important; }
</style>
<div cond="$mi->use_category=='Y' && $mi->cnb!='N'" class="if_lst_btn"|cond="$mi->select_lst=='N'" style="{$mh_cnb_style}">
{@ $cate_list = array(); $current_key = null; }
<!--@foreach($category_list as $key=>$val)-->
<!--@if(!$val->depth)-->
{@
$cate_list[$key] = $val;
$cate_list[$key]->children = array();
$current_key = $key;
}
<!--@elseif($current_key)-->
{@ $cate_list[$current_key]->children[] = $val }
<!--@end-->
<!--@end-->
~ 생략
<block cond="$mi->cnb=='cDrill'">
<load target="js/mh_category_drill.js" />
{@
$mh_cnb_flat = array();
foreach($category_list as $mh_ck=>$mh_cv):
$mh_cnb_flat[] = array(
'srl' => (int)$mh_cv->category_srl,
'parent' => (int)$mh_cv->parent_srl,
'title' => $mh_cv->title,
'count' => (int)$mh_cv->document_count,
'color' => $mh_cv->color
);
endforeach;
$mh_cnb_json = json_encode($mh_cnb_flat, JSON_HEX_TAG|JSON_UNESCAPED_UNICODE);
}
<div class="mh_cnb_drill" data-current_category="{$category}" data-total_count="{$total_count}" data-list_url_tpl="{getUrl('category','__MH_CATEGORY__','page','','document_srl','')}">
<script type="application/json" class="mh_cnb_data">{$mh_cnb_json}</script>
<div class="mh_cnb_levels"></div>
</div>
<style>
.mh_cnb_drill{margin-bottom:15px}
.mh_cnb_levels{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.mh_cnb_levels button.mh_cnb_pill{display:inline-block;padding:6px 14px;border:0;border-radius:20px;background:#f2f2f2;color:#555;font-size:13px;cursor:pointer;transition:background .15s}
.mh_cnb_levels button.mh_cnb_pill:hover{background:#e6e6e6}
.mh_cnb_levels button.mh_cnb_pill.on{background:#333;color:#fff}
.mh_cnb_levels select{padding:7px 10px;border:1px solid #ddd;border-radius:6px;background:#fff;color:#333;font-size:13px;min-width:130px;max-width:220px}
.mh_cnb_levels select:focus{outline:none;border-color:#999}
</style>
</block>
</div>
<!--// 상단 확장변수 버튼 (분류1/분류2 확장변수 ID: $mi->tag_cate1n / $mi->tag_cate2n 기반) -->
<block cond="$mi->top_tag_btn =='Y'">
{@
$mh_all_keys = DocumentModel::getExtraKeys($mi->module_srl);
$mh_c1_eid = $mi->tag_cate1n;
$mh_c1_idx = 0;
$mh_c1_options = array();
if($mh_c1_eid):
foreach($mh_all_keys as $mh_k_idx=>$mh_k):
if($mh_k->eid == $mh_c1_eid):
$mh_c1_idx = $mh_k_idx;
$mh_c1_options = $mh_k->options ? $mh_k->options : array();
break;
endif;
endforeach;
endif;
$mh_c1_counts = array();
if($mh_c1_idx):
$oDB = DB::getInstance();
$mh_c1_stmt = $oDB->query(
'SELECT value, COUNT(*) as cnt FROM document_extra_vars WHERE module_srl = ? AND eid = ? GROUP BY value',
(int)$mi->module_srl,
$mh_c1_eid
);
foreach($mh_c1_stmt->fetchAll() as $mh_c1_row):
$mh_c1_row = is_array($mh_c1_row) ? (object)$mh_c1_row : $mh_c1_row;
$mh_c1_counts[$mh_c1_row->value] = (int)$mh_c1_row->cnt;
endforeach;
endif;
$mh_c2_eid = $mi->tag_cate2n;
$mh_c2_idx = 0;
$mh_c2_options = array();
if($mh_c2_eid):
foreach($mh_all_keys as $mh_k_idx=>$mh_k):
if($mh_k->eid == $mh_c2_eid):
$mh_c2_idx = $mh_k_idx;
$mh_c2_options = $mh_k->options ? $mh_k->options : array();
break;
endif;
endforeach;
endif;
$mh_c2_counts = array();
if($mh_c2_idx):
$oDB = DB::getInstance();
$mh_c2_stmt = $oDB->query(
'SELECT value, COUNT(*) as cnt FROM document_extra_vars WHERE module_srl = ? AND eid = ? GROUP BY value',
(int)$mi->module_srl,
$mh_c2_eid
);
foreach($mh_c2_stmt->fetchAll() as $mh_c2_row):
$mh_c2_row = is_array($mh_c2_row) ? (object)$mh_c2_row : $mh_c2_row;
$mh_c2_counts[$mh_c2_row->value] = (int)$mh_c2_row->cnt;
endforeach;
endif;
}
{@
$mh_bridge_url = '/modules/board/skins/sketchbook5_MH/mh_facet_intersect.php';
$mh_cur_c1v = (string)Context::get('mh_c1v');
$mh_cur_c2v = (string)Context::get('mh_c2v');
}
<div cond="$mh_c1_idx || $mh_c2_idx" class="atag-wrap" style="order:{$mh_tag_order};margin-top:0;margin-bottom:0">
<div cond="$mh_c1_idx" class="atag-row">
<a href="{$mh_bridge_url.'?'.http_build_query(array('mid'=>$mi->mid,'eid1'=>$mh_c1_eid,'c1v'=>'','eid2'=>$mh_c2_eid,'c2v'=>$mh_cur_c2v))}" class="tag tbtn{!$mh_cur_c1v ? ' on' : ''}"><i class="fa fa-tag"></i> {$mi->tag_cate1}</a>
<a loop="$mh_c1_options=>$mh_ov1" href="{$mh_bridge_url.'?'.http_build_query(array('mid'=>$mi->mid,'eid1'=>$mh_c1_eid,'c1v'=>($mh_cur_c1v==$mh_ov1 ? '' : $mh_ov1),'eid2'=>$mh_c2_eid,'c2v'=>$mh_cur_c2v))}" class="tag tbtn{$mh_cur_c1v==$mh_ov1 ? ' on' : ''}">{$mh_ov1} ({(int)($mh_c1_counts[$mh_ov1] ?? 0)})</a>
</div>
<div cond="$mh_c2_idx" class="atag-row">
<a href="{$mh_bridge_url.'?'.http_build_query(array('mid'=>$mi->mid,'eid1'=>$mh_c1_eid,'c1v'=>$mh_cur_c1v,'eid2'=>$mh_c2_eid,'c2v'=>''))}" class="tag tbtn{!$mh_cur_c2v ? ' on' : ''}"><i class="fa fa-tag"></i> {$mi->tag_cate2}</a>
<a loop="$mh_c2_options=>$mh_ov2" href="{$mh_bridge_url.'?'.http_build_query(array('mid'=>$mi->mid,'eid1'=>$mh_c1_eid,'c1v'=>$mh_cur_c1v,'eid2'=>$mh_c2_eid,'c2v'=>($mh_cur_c2v==$mh_ov2 ? '' : $mh_ov2)))}" class="tag tbtn{$mh_cur_c2v==$mh_ov2 ? ' on' : ''}">{$mh_ov2} ({(int)($mh_c2_counts[$mh_ov2] ?? 0)})</a>
</div>
<style>
.atag-wrap { display:flex; flex-direction:column; gap:8px; margin:10px 5px 10px 0; }
.atag-row { display:flex; align-items:center; flex-wrap:wrap; gap:6px; }
.atag-wrap .tag.tbtn { display:inline-block;padding:6px 14px;border:0;border-radius:20px;background:#f2f2f2;color:#555;font-size:13px;text-decoration:none; }
.atag-wrap .tag.tbtn.on { background:#333;color:#fff; }
</style>
</div>
</block>
</div>
8. write_form.html 수정
<!--// 상단 : 카테고리, 제목 -->
<table class="bd_wrt_hd bd_tb">
<tr>
<td>
<block cond="$mi->use_category=='Y' && $category_list && $mi->use_admindong_category=='Y'">
{@
$mh_cat_flat = array();
foreach((array)$category_list as $mh_cv):
$mh_cat_flat[] = array(
'srl' => (int)$mh_cv->category_srl,
'parent' => (int)$mh_cv->parent_srl,
'depth' => (int)$mh_cv->depth,
'title' => $mh_cv->title,
'grant' => (bool)$mh_cv->grant
);
endforeach;
$mh_cat_json = json_encode($mh_cat_flat, JSON_HEX_TAG|JSON_UNESCAPED_UNICODE);
}
<load target="js/mh_category_cascade.js" type="body" />
<div class="mh_category_cascade" data-target="[name='category_srl']" data-current="{$oDocument->get('category_srl')}">
<script type="application/json" class="mh_category_cascade_data">{$mh_cat_json}</script>
</div>
<style>
.mh_category_cascade { display:flex; flex-direction:column; }
.mh_category_cascade select.mh_category_select { margin-bottom:5px; margin-right:5px; }
.mh_category_cascade select.mh_category_select:last-child { margin-bottom:0; }
</style>
</block>
<select cond="$mi->use_category=='Y' && $category_list" name="category_srl" class="category" style="display:none"|cond="$mi->use_admindong_category=='Y'">
<option value="">{$lang->category}</option>
<!--@foreach($category_list as $val)-->
<option value="{$val->category_srl}" disabled="disabled"|cond="!$val->grant" selected="selected"|cond="$val->grant && (($val->selected && !$oDocument->get('category_srl')) || $val->category_srl == $oDocument->get('category_srl'))">
{str_repeat(' ', $val->depth)} {$val->title} ({$val->document_count})
</option>
<!--@endforeach-->
</select>
</td>
<!--@if($mi->display_title =='')-->
<td width="100%">
<span class="itx_wrp">
<label for="postTitle">{$lang->title}</label>
<input type="text" name="title" class="itx" id="postTitle" title="{$lang->title}" value="{(!$oDocument->isExists()&&$mi->title_default_text)?htmlspecialchars($mi->title_default_text):$oDocument->getTitleText()}"/>
</span>
</td>
<td cond="$is_logged && !$oDocument->isExists() || $oDocument->get('status')=='TEMP'" class="m_no"><button type="button" class="bd_btn" onclick="doDocumentLoad(this);">{$lang->cmd_load}</button></td>
<!--@else-->
<input type="hidden" name="content" value=" " />
<!--@end-->
</tr>
</table>
9. 업로드를 마치고 해당 게시판(module_srl=xx)에 확장변수를 추가하고 검색을 Y로 해야 됩니다.
확장 변수와 분류를 함께 사용해서 선택 검색을 완성하는 것 입니다.
행정동만 사용할려면 안 만들어도 상관 없습니다.

10. 게시판 스킨 카테고리 메뉴 스타일을 "드릴다운 스타일"로 선택하고 아래 항목들을 선택합니다.

11. 설정을 마치고 글 작성을 누르면 제목 좌측에 행정동 분류가 출력되고, 확장변수를 선택합니다.

12. 글 작성 후 목록 상단에 확장변수와 분류 목록이 나옵니다.

13. 분류들을 선택하면 해당 목록만 출력됩니다.

확장변수 선택 기능을 사용하지 않고 행정동만 사용할려면 좀 더 간단합니다.
이 기능들은 특별한 일부의 게시판에만 사용하는 경우라 sketchbook5_MH 게시판 스킨에 추가하지 않고 Tip 으로만 남깁니다.
Tip이 도움이 되었다면 댓글과 평가 부탁합니다.
Tip에 대한 궁금한 점은 댓글로 남겨 주시면 성심껏 답변 드립니다. 
