Chào các bạn, hôm nay Wolfactive tụi mình sẽ giới thiệu cho các bạn về Mega Menu trên WordPress. Hiện nay Mega Menu trên WordPress cũng đã có nhiều Plugin hỗ trợ cho việc này. Tuy nhiên đối với bạn nào muốn tự tay mình làm một cái theo ý mình thì các bạn có thể tham khảo cách làm ở bài này nhé. Giờ chúng ta cùng nhau làm một Mega Menu nhé.
1/ Chuẩn bị
Trước khi chúng ta bắt đầu làm Mega Menu, thì đầu tiên các bạn cần phải Custom Link Api trước cái đã.
add_action('rest_api_init','apiCategory');
function apiCategory(){
register_rest_route('category-api/v1','/cat-name',array(
'methods' => "POST",
'callback' => 'renderCategoryAPI',
));
}
function renderCategoryAPI( $request ) {
// Here we are accessing the path variable 'id' from the $request.
$submit = prefix_apiCategory();
return rest_ensure_response( $submit );
}
// A simple function that grabs a book title from our blogsby ID.
function prefix_apiCategory() {
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
if ($contentType === "application/json") {
//Receive the RAW post data.
$content = trim(file_get_contents("php://input"));
$decoded = json_decode($content, true);
// setup default result data
$result = array();
if(isset($decoded['category']) && $decoded['category']){
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'category_name'=> $decoded['category'],
'showposts'=> 6,
);
$category_post = new WP_Query($args);
if ($category_post->have_posts()) {
while($category_post->have_posts()):$category_post->the_post();
array_push($result,array(
'title' => get_the_title(),
'thumbnail' => hk_get_thumb(get_the_id(),240,170),
'date' => get_the_date(),
'link' => get_the_permalink(),
));
endwhile;
}
}
}
// return result as json
wolfactive_return_json($result);
}
// Helper function to submit
function wolfactive_return_json( $php_array ) {
// encode result as json string
$json_result = json_encode( $php_array );
// return result
die( $json_result );
// stop all other processing
exit;
}
Okie vậy là xong, tuy nhiên custom api lần này của mình lại có điểm khác so với những lần trước. Sau khi mình Custom Link Api xong thì mình tạo một biến để lưu dữ liệu của hàm prefix_apiCategory().
Trong hàm prefix_apiCategory này, đầu tiên mình sẽ thực hiện kiểm tra xem $_SERVER[“CONTENT_TYPE”] có tồn tại và có data trong đó hay không, thông qua isset và trim. Khi kiểm tra đạt đủ hai điều kiện đó thì mình bắt đầu đưa data vào biến $contentType. Sau đó mình kiểm tra điều kiện của $contentType và lấy toàn bộ dữ liệu post rồi đưa nó về dạng json bằng json_decode().
2/ Tạo Mega Menu.
<ul id="menu-main-menu" class="menu"><li id="menu-item-43" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-home current-menu-item page_item page-item-2 current_page_item menu-item-43 active "><a href="#" aria-current="page">Trang Chủ</a></li>
<li id="menu-item-44" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-44"><a href="#">Công Nghệ</a>
<ul class="sub-menu">
<li id="menu-item-45" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-45"><a href="#">Điện Thoại</a></li>
<li id="menu-item-46" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-46"><a href="#">Máy Tính</a></li>
</ul>
</li>
<li id="menu-item-47" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-47"><a href="#">LifeStyle</a>
<ul class="sub-menu">
<li id="menu-item-48" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-48"><a href="#">Music</a></li>
</li>
<li id="menu-item-49" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-49"><a href="#">Thiết Kế</a>
<ul class="sub-menu">
<li id="menu-item-50" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-50"><a href="#">Kiến Trúc</a></li>
</ul>
</li>
<li id="menu-item-51" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-51"><a href="#">Thời Trang</a>
<ul class="sub-menu">
<li id="menu-item-52" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-52"><a href="#">Đường Phố</a></li>
<li id="menu-item-53" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-53"><a href="#">Mới</a></li>
<li id="menu-item-54" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-54"><a href="#">Tin Tức</a></li>
<li id="menu-item-55" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-55"><a href="#">Thế Giới</a></li>
<li id="menu-item-56" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-56"><a href="#">Xã Hội</a></li>
</ul>
</li>
</ul>
Ở trên chính là đoạn code mà WordPress đã tạo menu cho các bạn. Tuy nhiên các bạn cần phải css lại cho hợp lí (dưới đây là code mình viết theo scss nhé).
ul.menu {
display: flex;
justify-content: space-between;
li.menu-item {
padding: 20px 0;
margin-right: 20px;
a {
font-weight: 700;
font-size: 14px;
font-family: open-sanrif;
text-transform: uppercase;
}
.sub-menu {
display: none;
position: absolute;
background: #fff;
box-shadow: 1px 3px 9px 0px #aaaaaa82;
width: 100%;
z-index: 2;
top: 55px;
left: 0;
opacity: 0;
-webkit-animation: slide-top 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
animation: slide-top 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
border-radius: 0px 0px 10px 10px;
grid-template-columns: repeat(7, 1fr);
grid-template-rows: repeat(7, 1fr);
height: 340px;
padding: 25px;
.menu-item {
padding: 0 10px;
margin: 10px 0;
}
}
}
li.menu-item:hover {
a {
color: var(--primary-color);
}
transition: all .5s linear;
cursor: pointer;
.sub-menu {
display: grid;
opacity: 1;
.menu-item-object-category {
grid-column: 1 / span 1;
a {
color: #111;
}
a:hover {
color: var(--primary-color);
}
}
}
}
}
Sau khi css xong Hover vào, Menu của các bạn sẽ như sau:
Bây giờ chúng ta bắt đầu lấy API và đổ dữ liệu lên cái menu này nhé!
Đầu tiên các bạn tạo một file js để thực hiện việc lấy fetch API. Sau đó tạo những biến sau:
/*VARIABLES*/
var subMenuArray = document.querySelectorAll('.header .menu>.menu-item>.sub-menu');
var menuArray = document.querySelectorAll('.header .menu>.menu-item');
var urlImages = `${protocol}//${hostname}/wp-content/themes/wolfactive-newspaper/core/assets/images/`;
var protocol = window.location.protocol;
var hostname = window.location.hostname;
if (subMenuArray.length != 0 && menuArray.length != 0)
menuArray.forEach((item, i) => {
item.onmouseenter = (event) => {
let postDisplay = document.querySelector('.display--post');
if (postDisplay) {
postDisplay.remove();
}
let checkLengthMenu = event.srcElement.children;
if (checkLengthMenu.length > 1) {
let category = convertTextToSlug(event);
let showPost = createPostList(checkLengthMenu[1]);
getListPost(category, showPost);
}
}
});
Để tránh bị lỗi khi thực hiện fetch, thì mình cần phải check length xem menuArray và subMenuArray có độ dài lớn hơn 0 hay không. Nếu có thì mình mới bắt đầu duyệt từng MenuArray bằng foreach. Trong forEach mình sẽ bắt đầu bắt sử kiện item.onmouseenter, sự kiện này cho phép khi chúng ta hover vào thì sẽ cho phép chúng ta thực hiện code ở bên trong. Ở đoạn code trên của mình sẽ như sau, khi mình hover vào thì nó sẽ thực thi function và có biến event trong đó. Biến Event ở đây giống như với con trỏ this đấy
Bước tiếp theo mình lại tiếp tục tạo biến postDisplay và DOM đến class display–post. Rồi mình lại tiếp tục kiểm tra xem, thẻ div có class display–post này có tồn tại hay không, nếu có thì xóa nó đi, để khi các bạn hover vào menu-item khác thì nó sẽ load lại cái mới và không bị đè post lên hoặc tạo quá nhiều postDisplay. Tuy nhiên lúc này mình chưa tạo thẻ div có chứa class display–post nhé.
Sau khi mình kiểm tra, mình sẽ bắt đầu xử lí data bằng cách lấy dữ liệu từ srcElement.children. Để có thể lấy các thẻ bên trong thẻ li.menu-item, các bạn có thể dùng consol.log(event) để xem nhé.
Rồi mình lại tiếp tục kiểm tra xem length của children có lớn hơn 1 hay không. Nếu có thì mình bắt đầu chuyển nội dung thẻ a về dạng slug. thông qua hàm converTextToSlug của mình. Rồi mình lại tiếp tục tạo postList và fetch API. Dưới đây sẽ là các function để hỗ trợ cho đoạn code trên của mình:
function converTextToSlug
Mình tạo ra nhằm chuyển đổi nội dung của thẻ a về dạng slug để tiện cho viêc lấy api, childNode[0].innerHTML chính là nơi để bạn lấy nội dung của thẻ a. và sau đó mình xóa khoảng trắng đi và tạo một biến string để lưu slug lại, nếu slug lớn hơn hoặc bằng 2 kí tự thì có thêm “-“. Ở đây các bạn chú ý nhé, tiêu đề và slug chúng đều gần giống nhau cả, nếu các bạn thay đổi tiêu đề thì nên thay đổi luôn slug để tránh tình trạng không DOM đến được.
function convertTextToSlug(event) {
let categoryText = event.srcElement.childNodes[0].innerHTML.toLowerCase();
let categoryArray = categoryText.split(" ");
let category = ``;
if (categoryArray.length > 1) {
category = categoryArray.join('-');
} else if (categoryArray.length === 1) {
category = categoryArray[0];
}
return category;
}
function createPostList
function truyền vào một biến item chính là nơi mà bạn sẽ tạo element, ở đây như nãy mình nói, mình tạo một div element có chứa class là display–post để show kết quả và class loading để show ảnh gif của mình trong lúc nó fetch. Rồi cuối cùng mình return giá trị.
function createPostList(item) {
let postDisplay = document.createElement("div");
item.appendChild(postDisplay);
postDisplay.classList.add('display--post');
postDisplay.classList.add('loading');
return postDisplay;
}
createFlick
ở đây mình dùng thư viện slideshow của flick để DOM tới và tạo slide ra cho bắt mắt hơn, các bạn có thể xem flick và download tại đây nếu muốn. Còn không bạn có thể xài cái khác, tùy ý các bạn.
function createFlick() {
var flick = document.querySelector('.display--post');
var flkty = new Flickity(flick, {
// options
draggable: true,
pageDots: false,
cellAlign: 'left',
contain: true,
groupCells: 3,
});
}
getListPost
function này giúp các bạn lấy dữ liệu ra, ở đây trước khi nó lấy dữ liệu, mình đặt thêm một img để tạo ra ảnh loading trước khi fetch. Nhằm để trải nghiệm người dùng tốt hơn. Kế tiếp mình setTimeOut sau 1s thì nó sẽ xóa class Loading này đi để ẩn hình ảnh. Rồi show kết quả ra thông qua biến slidePost. Kế tiếp mình bắt đầu kiểm tra điều kiện nếu như tại nơi mình hover mà có số bài post lớn hơn 3 thì bắt đầu tạo flick, ngược lại thì không làm gì cả, còn nếu = 0 thì show không có kết quả.
function getListPost(category, showPost) {
let apiUrlCat = `${protocol}//${hostname}/wp-json/category-api/v1/cat-name`;
fetch(apiUrlCat, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json', // sent request
'Accept': 'application/json' // expected data sent back
},
body: JSON.stringify({ 'category': category })
})
.then(response => response.json())
.then(data => {
showPost.innerHTML = `<img style="width: 64px; height: 64px;" src="${urlImages}Dual-Ring-1s-200px.gif" alt="Loading Image" />`;
setTimeout(function() {
showPost.classList.remove("loading");
let slidePost = displayPost(data, showPost);
if (Object.keys(data).length > 3) {
createFlick();
} else if (Object.keys(data).length === 0) {
showPost.innerHTML = `Không Có Bài Viết`;
}
getListPostChild(slidePost);
}, 1000);
})
.catch(err => console.log(err));
}
displayPost: đưa dữ liệu ra ngoài html.
function displayPost(data, showPost) {
let content = ``;
data.forEach((item, i) => {
content += `
<div class="display--post-item">
<div class="post-item-thumbnail">
<a href="${item.link}"><img src="${item.thumbnail}" alt="image"></a>
</div>
<div class="post-item-title">
<a href="${item.link}">${item.title}</a>
</div>
<div class="post-item-date">
${item.date}
</div>
</div>`;
});
showPost.innerHTML = content;
return showPost;
}
getListPostChild
là nơi các bạn hover tới các menu-item trong sub-menu đấy. về cách hoạt động thì giống như với cách mình viết xử lí ở menu-item chứa sub-menu ở trên thôi.
function getListPostChild(slidePost) {
let listSubMenuArray = document.querySelectorAll('.header .menu>.menu-item>.sub-menu>.menu-item');
// console.log(slidePost);
listSubMenuArray.forEach((item, i) => {
item.onmouseenter = (event) => {
let postDisplay = document.querySelector('.display--post');
if (postDisplay) {
postDisplay.remove();
}
let checkLengthMenu = event.srcElement.children;
let category = convertTextToSlug(event);
let showPost = createPostList(checkLengthMenu[0]);
getListPost(category, showPost);
}
});
}
Cuối cùng thì các bạn Css lại cho đẹp nhé.
.display--post {
position: absolute;
top: 0;
display: flex;
right: 0px;
height: 100%;
width: 100%;
grid-column: 2 / span 7;
padding: 30px;
border-left: 1px solid #aaaaaa50;
.flickity-viewport {
height: 100% !important;
.flickity-slider {
width: 110%;
.display--post-item {
width: 250px;
.post-item-title {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
}
}
.display--post-item {
width: 250px;
margin-right: 20px;
.post-item-title {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.flickity-button {
border: 1px solid var(--tertiary-color);
}
.flickity-prev-next-button {
width: 30px;
height: 30px;
border-radius: 0px;
}
/* icon color */
.flickity-button-icon {
fill: var(--tertiary-color);
left: 30%;
width: 40%;
}
/* position outside */
.flickity-prev-next-button.previous {
left: 30px;
right: unset;
top: unset;
bottom: 10px;
width: 25px;
height: 25px;
}
.flickity-prev-next-button.next {
right: unset;
left: 65px;
width: 25px;
height: 25px;
bottom: 10px;
top: unset;
}
}
Và đây là kết quả của chúng ta:
Tổng kết
Như vậy, mình đã hướng dẫn cho các bạn về cách làm Mega Menu trên WordPress bằng cách custom link api và fetch api bằng javascript để lấy dữ liệu ra kèm theo việc bắt sự kiện hover vào menu để thực thi dữ liệu. Các bạn có thể custom lại theo ý các bạn nhé. Mọi thông tin thắc mắc, các bạn cứ liên hệ fanpage : https://www.facebook.com/Wolfactiveweb.design.SEO/ tụi mình để được giải đáp nhé. Chúc các bạn thành công và WolfActive chúc các bạn một ngày vui vẻ nhé <3 .