我們都知道 WordPress 的 Category 理論上可以有無限多層,並且一個 Post 可以屬於多個不同的 Category (跨多個主子分類),這在某些應用情境裡會造成很多困擾。
我們今天要實作的情境是:
- 只能有兩層分類
- Post 只能有一個父層分類和只能有一個子層分類
限制兩層分類
我們可以使用 taxonomy_parent_dropdown_args
這個來限制下拉選單中可設定的父層分類僅有第一層
add_filter('taxonomy_parent_dropdown_args', function($args, $taxonomy, $context) {
if ($taxonomy == 'category') {
$args['depth'] = 1;
}
return $args;
}, 10, 3);
Post 只能有一個父層分類和只能有一個子層分類
- 使用 ACF 開設分類法,並且將 metabox 關掉。
- 使用 ACF 開設兩個欄位,分別為 main_category 和 sub_category 其類型為 select
- 使用
acf/load_field
filter 來產生選項
function acf_load_main_category_field_choices( $field ) {
$field['choices'] = array();
$terms = get_terms([
'taxonomy' => 'category',
'parent' => 0,
'hide_empty' => false,
]);
$field['choices'][ 0 ] = '請選擇';
foreach ($terms as $term) {
$field['choices'][ $term->term_id ] = $term->name;
}
return $field;
}
add_filter('acf/load_field/key=field_658d1a664afbb', 'acf_load_main_category_field_choices');
function acf_load_sub_category_field_choices( $field ) {
$field['choices'] = array();
$terms = array_filter(get_terms([
'taxonomy' => 'category',
'hide_empty' => false,
]), function($term) {
return $term->parent != 0;
});
$field['choices'][ 0 ] = '請選擇';
foreach ($terms as $term) {
$field['choices'][ $term->term_id ] = $term->name;
}
return $field;
}
add_filter('acf/load_field/key=field_658d1a8f4afbc', 'acf_load_sub_category_field_choices');
- 建立 category 樹狀結構
function digitspark_get_taxonomy_array($taxonomy) {
$terms = get_terms([
'taxonomy' => 'user-guide-category',
'hide_empty' => false,
]);
return digitspark_get_taxonomy_tree($terms, 0);
}
function digitspark_get_taxonomy_tree($nodes, $parentId) {
$tree = [];
if (is_array($nodes)) {
$roots = array_filter($nodes, function($node) use ($parentId) {
return $node->parent == $parentId;
});
$index = 0;
foreach ($roots as $rnode) {
$tree[$index++] = [
'node' => $rnode,
'children' => digitspark_get_taxonomy_tree($nodes, $rnode->term_id)
];
}
}
return $tree;
}
- 使用 4 的樹狀搭配 JavaScript 來控制前端選項
function acf_load_categories_js() {
$items = digitspark_get_taxonomy_array('category');
?>
<script type="text/javascript">
(function($) {
var categories_tree = <?php echo json_encode($items); ?>;
function updateSubCategories() {
// 當主分類被選的時候,更新子分類的選項
initSelection()
// 如果確定父分類有被選,則要顯示子分類的選項,並修改子分類為空
const parent = parseInt(window.jQuery('#acf-field_658d1a664afbb').val())
if (parent) {
const key = '#acf-field_658d1a8f4afbc option[value=0]'
window.jQuery(key).show()
window.jQuery('#acf-field_658d1a8f4afbc').show()
window.jQuery('#acf-field_658d1a8f4afbc').val(0)
}
}
window.jQuery('#acf-field_658d1a664afbb').change(updateSubCategories)
function initSelection() {
const parent = parseInt(window.jQuery('#acf-field_658d1a664afbb').val())
if (parent) {
// find children in tree
const node = categories_tree.filter(function(item) {
return item.node.term_id == parent
})
// hide those options
window.jQuery('#acf-field_658d1a8f4afbc option').hide()
node[0].children.forEach(function(item) {
const key = '#acf-field_658d1a8f4afbc option[value="' + item.node.term_id + '"]'
window.jQuery(key).show()
})
} else {
window.jQuery('#acf-field_658d1a8f4afbc').hide()
}
}
})(jQuery);
</script>
<?php
}
add_action('acf/input/admin_footer', 'acf_load_user_guide_categories_js');