Các công ty hiện nay hay các chuỗi bán hàng thường mở nhiều chi nhánh trên khắp các tỉnh thành hoặc trong thành phố. Các bạn thiết kế web sẽ gặp trường hợp tạo chức năng “Hệ thống chi nhánh” cho các website qua google map. Nhưng API google map thì lại bắt trả phí để sử dụng làm hệ thống chi nhánh.
Với bài viết sau đây hnitmedia sẽ hướng dẫn các bạn tự làm chức năng hệ thống chi nhánh với google map cơ bản nhất không cần dùng API
Phần 1: Hướng dẫn tạo hệ thống chi nhánh với Google Maps
Bước 1: Tạo Shortcode
Chúng ta sẽ tạo short để có thể sử dụng linh hoạt ở bất cứ đâu bạn muốn. Trong code này sẽ viết các mã HTML, CSS, JAVASCRIPT
<?php
add_shortcode('hnit_map_local', function(){
ob_start();
?>
// Code tại đây
<?php
wp_reset_postdata();
$content = ob_get_contents();
ob_end_clean();
return $content;
});
?>
Bước 02: Tạo cấu trúc HTML với 2 cột
Đặt code vào trong shortcode trên, bên trái sẽ là danh sách các địa điểm, bên phải sẽ là bản đồ
<!-- HTML -->
<div class="pk_row">
<div class="pk_column pk-column-data">
</div>
<div class="pk_column pk-column-map">
</div>
</div>
<!-- CSS-->
<style>
.pk_row {
height: 600px;
}
.pk_column {
float: left;
width: 50%;
padding: 10px;
height:100%;
background:white;
border: 5px solid #ccc;
}
.pk_row:after {
content: "";
display: table;
clear: both;
}
</style>
Bước 03: Tạo danh sách dữ liệu trong cột bên trái (pk-column-data)
Mình sẽ demo bằng dữ liệu danh sách cửa hàng Mediamart Hà Nội. Các bạn lưu ý đặt dữ liệu theo cấu trúc mẫu phía dưới, các thẻ <li> có thuộc tính data-map
chính là src mã google maps embed chúng ta lấy ở trên https://google.com/maps
<!-- HTML -->
<ul id="pk_list_map">
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d5264.906561212957!2d105.81119061764379!3d21.078607217417975!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135ab633709e735:0xbce4be42931e79d7!2zTWVkaWFtYXJ0IEzhuqFjIExvbmcgUXXDom4!5e0!3m2!1svi!2sus!4v1722133884574!5m2!1svi!2sus">
672 Lạc Long Quân, Nhật Tân, Tây Hồ, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3130.8015597413832!2d105.80639835563994!3d21.065811820350714!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135aae689795b9b:0x39e9258e56203cb9!2zU2nDqnUgdGjhu4sgxJBp4buHbiBtw6F5IE1lZGlhIE1hcnQ!5e0!3m2!1svi!2sus!4v1722133932522!5m2!1svi!2sus">
583 Lạc Long Quân, Xuân La, Tây Hồ, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2214.094906617745!2d105.80137716234678!3d21.046728308119164!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135ab8c9f51eeef:0x7c09768e45ee74cb!2sMedia Mart!5e0!3m2!1svi!2sus!4v1722133970512!5m2!1svi!2sus">
16 Hoàng Quốc Việt, Nghĩa Đô, Cầu Giấy, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3833.2027027936233!2d105.76428382213098!3d21.029529258588838!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x313454baf43641c3:0x75277794a7c0b824!2zU2nDqnUgdGjhu4sgxJBp4buHbiBNw6F5IE1lZGlhIE1hcnQ!5e0!3m2!1svi!2sus!4v1722134009397!5m2!1svi!2sus">
16 Lê Đức Thọ, Nam Từ Liêm, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3415.3947807557834!2d105.79690308937232!3d21.012093216795318!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135ab9708dddbc1:0xc72f7e9f994503e2!2zTWVkaWFtYXJ0IFRy4bqnbiBEdXkgSMawbmc!5e0!3m2!1svi!2sus!4v1722137271657!5m2!1svi!2sus">
40 Trần Duy Hưng, Cầu Giấy, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3415.326610172044!2d105.80953837029361!3d21.015070348300892!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135abdc18b626a9:0x9d51f4ff9c19e8ad!2zTWVkaWFtYXJ0IEzDoW5nIEjhuqE!5e0!3m2!1svi!2sus!4v1722137304395!5m2!1svi!2sus">
26 Láng Hạ, Đống Đa, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
<li data-map="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3131.4436900706596!2d105.81549671021469!3d21.035282000583322!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135abce85df50c1:0x5f86be6355b1e2c5!2zU2nDqnUgVGjhu4sgxJBp4buHbiBNw6F5IE1lZGlhTWFydCDEkOG7mWkgQ-G6pW4!5e0!3m2!1svi!2s!4v1722137779617!5m2!1svi!2s">
218 Đội Cấn, Ba Đình, Hà Nội <br>
☏ <i><small>0986209305</small></i>
</li>
</ul>
<!-- CSS -->
<style>
.pk-column-data ul li {
margin-bottom:5px;
margin-left:0;
padding: 10px;
padding-left:15px;
background: #efefefc7;
list-style-type:none;
}
.pk-column-data ul li:hover {
cursor: pointer;
background:#326e5130;
}
.pk-column-data ul li.pk-active {
background:#326e51;
color:white;
}
</style>
Bước 04: Chèn iframe google maps vào cột bên phải (pk-column-map)
Chúng ta sẽ lấy full cấu trúc embed mà trên google maps đã chia sẻ. Lưu ý thay đổi thuộc tính width, height cho phù hợp với khung đã tạo. Ở đây tôi đặt width=”100%” và height=”570″
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d5264.906561212957!2d105.81119061764379!3d21.078607217417975!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135ab633709e735:0xbce4be42931e79d7!2zTWVkaWFtYXJ0IEzhuqFjIExvbmcgUXXDom4!5e0!3m2!1svi!2sus!4v1722133884574!5m2!1svi!2sus" width="100%" height="570" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
Link iframe sẽ là địa chỉ mặc định đầu tiên ở danh sách trên. Lúc này giao diện hiển thị ra đã hoàn chỉnh, nhưng khi click vào địa chỉ thì chưa hoạt động, không có thay đổi gì. Chúng ta cần code thêm javascript.
Bước 05: Viết code Javascript để thực hiện thao tác
<script>
const list_map = document.querySelectorAll('#pk_list_map li')
list_map.forEach(item => {
item.addEventListener('click', function(){
var map = this.getAttribute('data-map')
document.querySelector('.pk-column-map iframe').src = map
list_map.forEach(e => e.classList.remove("pk-active"));
this.classList.add("pk-active");
})
});
</script>
Bước 06: Hoàn thành cấu trúc và chức năng
Vậy là đến bước này, đã có thể sử dụng tính năng một cách hoàn chỉnh về mặt cấu trúc + chức năng. Để thay đổi danh sách thì bạn quay lại bước 03 để cập nhật.
Phần 2: Quản lý dữ liệu chi nhánh trong Admin & phân theo tỉnh (thành), quận (huyện)
Ở phần 1 các bạn đã có thể tạo được 1 trang tĩnh cơ bản đủ chức năng mong muốn, tại phần 2 này, tôi sẽ hướng dẫn tạo phần quản lý chi nhánh trong Admin và nâng cấp bộ lọc địa chỉ. Các bạn cùng theo dõi.
Bước 01: Tạo Custom Post Type & Taxonomy
<?php
add_action('init', function(){
register_post_type('location-map',
array(
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-store',
'supports' => array('title', 'thumbnail'),
'labels' => array(
'name' => 'Chi nhánh',
'singular_name' => 'Chi nhánh',
'add_new_item' => 'Thêm mới chi nhánh',
'add_new' => 'Thêm mới',
'edit_item' => 'Sửa chi nhánh',
'featured_image' => 'Ảnh đại diện',
'set_featured_image' => 'Chọn ảnh',
'remove_featured_image' => 'Xóa ảnh',
'menu_name' => 'Chi nhánh'
),
)
);
register_taxonomy('province', 'location-map', [
'label' => 'Tỉnh thành',
'hierarchical' => true,
'show_admin_column' => true,
'show_in_rest' => true
]);
});
?>
Bước 02: Tạo thêm trường dữ liệu với add_meta_box
<?php
add_action('add_meta_boxes', function(){
add_meta_box('chi-nhanh', 'Thông tin chi nhánh', function($post){
$location_address = get_post_meta( $post->ID, 'location_address', true );
$location_phone = get_post_meta( $post->ID, 'location_phone', true );
$location_link = get_post_meta( $post->ID, 'location_link', true );
?>
<div class="input_custom">
<label for="location_address">Địa chỉ: </label>
<input type="text" id="location_address" name="location_address" placeholder="Địa chỉ" style="width:500px" value="<?php echo !empty($location_address) ? esc_attr($location_address) : null ?>" />
</div>
<div class="input_custom">
<label for="location_link">Link iframe: </label>
<input type="text" id="location_link" name="location_link" placeholder="Link iframe" style="width:500px" value="<?php echo !empty($location_link) ? esc_attr($location_link) : null ?>" />
</div>
<div class="input_custom">
<label for="location_phone">Số điện thoại: </label>
<input type="text" id="location_phone" name="location_phone" placeholder="Số điện thoại" style="width:500px" value="<?php echo !empty($location_phone) ? esc_attr($location_phone) : null ?>" />
</div>
<style>
.input_custom {
margin-bottom: 10px;
}
.input_custom label {
display:inline-block;
width: 100px;
}
</style>
<?php
}, 'location-map');
});
?>
Lưu dữ liệu từ custom meta box ở trên vào database
<?php
add_action('save_post', function($post_id){
if(!empty($_POST['location_address'])) {
$location_address = sanitize_text_field($_POST['location_address']);
update_post_meta($post_id, 'location_address', $location_address);
}
if(!empty($_POST['location_link'])) {
$location_link = sanitize_text_field($_POST['location_link']);
update_post_meta($post_id, 'location_link', $location_link);
}
if(!empty($_POST['location_phone'])) {
$location_phone = sanitize_text_field($_POST['location_phone']);
update_post_meta($post_id, 'location_phone', $location_phone);
}
});
?>
Bước 03: Get dữ liệu Tỉnh thành, Quận huyện + Chi nhánh
Ở giao diện bên ngoài, sẽ cần hiển thị danh sách các dữ liệu ở trên ra bên ngoài. Ở shortcode trong Bước 1 (Phần 1), đặt code sau để lấy dữ liệu cần thiết
/* Lấy tỉnh thành quận huyện */
$province = get_terms([
'taxonomy' => 'province',
'hide_empty' => false,
]);
/* Lấy danh sách chi nhánh */
$locations = get_posts([
'post_type' => 'location-map',
'post_status' => 'publish',
'posts_per_page' => -1
]);
Tạo cấu trúc Tỉnh thành – Quận huyện bằng Select Option và hiển thị dữ liệu đã lấy ở trên
<div class="pk_row">
<div class="pk_column pk-column-data">
<div class="province location">
<label for="">Tỉnh thành</label>
<select id="province">
<option value="">Lựa chọn</option>
<?php foreach($province as $item): if($item->parent == 0): ?>
<option value="<?php echo $item->term_id ?? null ?>"><?php echo $item->name ?? null ?></option>
<?php endif; endforeach; ?>
</select>
</div>
<div class="district location">
<label for="">Quận/Huyện</label>
<select id="district" disabled="">
<option value="">Lựa chọn</option>
<?php foreach($province as $item): if($item->parent > 0): ?>
<option value="<?php echo $item->term_id ?? null ?>" data-parent-id="<?php echo $item->parent ?? null ?>"><?php echo $item->name ?? null ?></option>
<?php endif; endforeach; ?>
</select>
</div>
<div class="clearfix"></div>
<hr>
<ul id="pk_list_map">
<?php
foreach($locations as $location):
$location_address = get_post_meta( $location->ID, 'location_address', true );
$location_phone = get_post_meta( $location->ID, 'location_phone', true );
$location_link = get_post_meta( $location->ID, 'location_link', true );
$terms = wp_get_post_terms($location->ID, 'province');
$ids = array_column($terms, 'term_id');
?>
<li data-term="<?php echo json_encode($ids) ?? null ?>" data-map="<?php echo $location_link ?? null ?>">
<p class="location_title"><?php echo $location->post_title ?? null ?></p>
<p class="location_address"><?php echo $location_address ?? null ?></p>
☏ <i><small><?php echo $location_phone ?? null ?></small></i>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="pk_column pk-column-map">
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1861.8583177565963!2d105.81213348044113!3d21.0440212023018!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3135ab105387c82f:0x743e0ea8682ba96c!2zVGjhur8gR2nhu5tpIERpIMSQ4buZbmc!5e0!3m2!1svi!2s!4v1722329069267!5m2!1svi!2s" width="100%" height="570" style="border:0;" allowfullscreen=""></iframe>
</div>
</div>
<style>
.pk-column-data ul li.pk-active, .pk-column-data ul li.pk-active .location_title {
background:#326e51;
color:white;
}
.location {
float: left;
width: 48%;
}
.clearfix {
clear:both;
}
#district option.hidden {
display:none;
}
.province {
margin-right: 20px;
}
.location_title, .location_address {
margin-bottom:2px;
}
.location_title {
font-weight: bold;
color:#326e51;
}
</style>
Bước 04: Sử dụng Javascript để tạo trải nghiệm
Những bước ở trên đã có thể tạo được giao diện hoàn chỉnh + dữ liệu hoàn chỉnh, nhưng thao tác sử dụng chưa có đúng ở bộ lọc, cần áp dụng thêm code javascript sau
<script>
const locations = document.querySelectorAll("#pk_list_map li");
const options = document.querySelectorAll("#district option");
const province = document.getElementById('province');
const district = document.getElementById('district');
document.getElementById('province').addEventListener('change', function() {
var province_id = this.value;
if(province_id == '') {
district.setAttribute('disabled', '')
locations.forEach(e => e.classList.remove('hidden'))
} else {
district.removeAttribute('disabled')
getDistrict(province_id)
getLocation(province_id)
}
district.selectedIndex = 0
});
function getDistrict(province_id) {
options.forEach(function(v){
var parent_id = v.getAttribute('data-parent-id')
if(v.hasAttribute('data-parent-id')) {
if(parent_id != province_id) {
v.classList.add("hidden")
} else {
v.classList.remove("hidden")
}
}
})
}
document.getElementById('district').addEventListener('change', function() {
var value = this.value;
getLocation(value)
});
function getLocation(term_id) {
var province_id = province.value;
locations.forEach(function(v){
var parent_id = v.getAttribute('data-term')
parent_id = JSON.parse(parent_id)
if(term_id == '') {
province_id = parseInt(province_id)
if(parent_id.includes(province_id)) {
v.classList.remove("hidden")
} else {
v.classList.add("hidden")
}
} else {
var parent_id = v.getAttribute('data-term')
parent_id = JSON.parse(parent_id)
term_id = parseInt(term_id)
if(parent_id.includes(term_id)) {
v.classList.remove("hidden")
} else {
v.classList.add("hidden")
}
}
})
}
const list_map = document.querySelectorAll('#pk_list_map li')
list_map.forEach(item => {
item.addEventListener('click', function(){
var map = this.getAttribute('data-map')
document.querySelector('.pk-column-map iframe').src = map
list_map.forEach(e => e.classList.remove('pk-active'))
this.classList.add('pk-active')
})
});
</script>
Kết thúc. Chúc các bạn thành công