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()
.
if(isset($decoded[‘category’]) && $decoded[‘category’])
rồi truyền nó vào trong query. Kế tiếp, khi mình đã add dữ liệu vào $result thì mình lại tiếp tục đưa nó về dạng json tiếp bằng hàm mình đã tạo trước đó wolfactive_return_json()
.2. Tạo Mega Menu.
Chắc hẳn các bạn cũng đã biết, đối với WP khi các bạn kéo vào menu các bạn thêm 1 submenu. WordPress sẽ tạo những class để hỗ trợ cho chúng ta trong việc Css, DOM lấy dữ liệu ra. Dưới đây là đoạn code menu mà WordPress đã tạo cho các bạn (tên menu phụ thuộc vào việc các bạn đặt như thế nào nhé).
<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/`;
Ở đoạn code trên mình DOM đến 2 nơi. MenuArray mình sẽ DOM đến tất cả các thẻ li có class là menu-item (Không bao gồm cả menu-item trong sub-menu). subMenuArray thì mình sẽ DOM đến tất cả thẻ ul có class là sub-menu. urlImage mình dùng để lấy ảnh gif loading của mình ra, trong đó biến protocal và hostname sẽ giúp mình đi vào các thư mục bên trong theme và vì mình viết ở nơi khác nên sẽ gọi như vậy. Còn các bạn nếu chưa có thì khai báo 2 biến này như sau nhé và sau đó các bạn nối chuỗi lại là được.
var protocol = window.location.protocol; var hostname = window.location.hostname;
Vậy là trước hết chúng ta đã chuẩn bị xong hết các biến cần làm rồi. Giờ thì bắt đầu xử lí thôi.
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:
Nguồn: https://wolfactive.dev/huong-dan-tao-mega-menu-tren-wordpress/
Quét mã QR để đọc bài viết này để xem tiếp trên điện thoại