Не часто, но иногда необходимо, когда на сайт требуется добавить несколько однотипной информации. К примеру в моем случае, на одном из моих сайтов, а сайт, это каталог прямых трансляций с веб-камер, добавлять на одну страницу несколько стримов, где бы они выводились в виде кнопок и можно было бы переключатся между ними.
И для одного стрима нужно указать тип, название и ссылку потока. И так как стримов может быть бесконечное количество, нужно было реализовать повторяющие мета поля или еще их называют repeatable meta fields. Идея была такая, мы создаем объект и заполняем его данными и преваращаем в json строку, которые помещаем в input поле.
В итоге вышло вот так вот:
Как это выглядит вживую можно посмотреть тут: Краснодар, панорама — Веб-камеры. А как это реализовать читаем ниже.
Создание класса
Создаем отдельный файл, я разместил в папке ./inc, которая находися в корневой директории темы и называл multiple-streams-meta-box.php
<?php
if (is_admin()) {
add_action('load-post.php', 'createMultipleStreamsMetaBox');
add_action('load-post-new.php', 'createMultipleStreamsMetaBox');
}
function createMultipleStreamsMetaBox()
{
new MultipleStreamsMetaBox();
}
class MultipleStreamsMetaBox
{
public function __construct()
{
add_action('add_meta_boxes', [$this, 'add_meta_box']);
add_action('save_post', [$this, 'save']);
}
public function add_meta_box($post_type)
{
// Limit meta box to certain post types.
$post_types = ['post'];
if (in_array($post_type, $post_types)) {
add_meta_box(
'multiple_streams',
__('Multiple Streams', 'worldviewstream'),
[$this, 'render_meta_box_content'], $post_type, 'normal', 'high'
);
}
}
public function save($post_id)
{
}
public function render_meta_box_content($post)
{
}
}
В данном примере, мы подготовили все необходимые методы, которые позволяют создавать мета поля для все постов. Дальше необходимо создать HTML разметку, для нашего повторяющего мета поле. Все это добавляем в метод render_meta_box_content
wp_nonce_field('multiple_streams_box', 'multiple_streams_box_nonce');
$value = htmlspecialchars(get_post_meta($post->ID, 'multiple_streams', true));
?>
<section class="multiple-streams">
<div class="acf-field">
<div class="acf-label">
<label for="stream_type"> Stream Type </label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<select id="stream_type" name="stream_type" >
<option value="youtube">Youtube</option>
<option value="stream">Stream</option>
<option value="name">Name</option>
<option value="iframe">Iframe</option>
<option value="wmsAuthSign">WmsAuthSign</option>
</select>
</div>
</div>
<div class="acf-label">
<label for="stream_url"> Stream URL </label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<input type="text" id="stream_url" name="stream_url" placeholder="Enter stream URL" >
</div>
</div>
<div class="acf-label">
<label for="stream_name"> Stream Name </label>
</div>
<div class="acf-input">
<div class="acf-input-wrap">
<input type="text" id="stream_name" name="stream_name" placeholder="Enter stream Name" >
</div>
</div>
</div>
<button id="add_one_more_stream">Add Stream</button>
<div class="stream-container" id="streamContainer">
<!-- Stream items will be added here -->
</div>
<div id="json-output" style="display: none;">
<strong>JSON Output:</strong>
<pre id="jsonOutput"></pre>
</div>
<input id="multiple_streams_data" type="text" name="multiple_streams_data" value="<?= $value; ?>" style="width: 100%;">
</section>
Теперь добавим все необходимые скрипты и стили, чтобы это работало и работало красиво! (Добавлять нужно сразу после HTML)
<script>
jQuery(function ($) {
var addStreamBtn = document.getElementById('add_one_more_stream');
addStreamBtn.addEventListener('click', function (e) {
e.preventDefault();
addStream();
});
loadJSON();
var removeBtns = document.querySelectorAll('.remove-btn');
if (removeBtns) {
removeBtns.forEach(function (removeBtn) {
removeBtn.addEventListener('click', function (e) {
removeStream(removeBtn);
})
})
}
function addStream() {
var streamType = document.getElementById('stream_type').value;
var streamUrl = document.getElementById('stream_url').value;
var streamName = document.getElementById('stream_name').value;
if (streamType && streamUrl && streamName) {
var streamContainer = document.getElementById('streamContainer');
var streamItem = document.createElement('div');
streamItem.className = 'stream-item';
streamItem.dataset.type = streamType;
streamItem.dataset.url = streamUrl;
streamItem.dataset.name = streamName;
streamItem.innerHTML = `
<div>
<strong>Type:</strong> ${streamType}, <strong>URL:</strong> ${streamUrl}, <strong>Name:</strong> ${streamName}
</div>
<span class="remove-btn">Remove</span>
`;
streamContainer.appendChild(streamItem);
// Очистить поля ввода
document.getElementById('stream_type').value = '';
document.getElementById('stream_url').value = '';
document.getElementById('stream_name').value = '';
// Показать JSON Output
document.getElementById('json-output').style.display = 'block';
// Обновить JSON Output
updateJSONOutput();
} else {
alert('Please enter both stream type and URL and Name.');
}
}
function removeStream(element) {
var streamItem = element.parentElement;
var streamContainer = document.getElementById('streamContainer');
streamContainer.removeChild(streamItem);
// Обновить JSON Output
updateJSONOutput();
}
function updateJSONOutput() {
var streamContainer = document.getElementById('streamContainer');
var streamItems = streamContainer.getElementsByClassName('stream-item');
var streams = [];
for (var i = 0; i < streamItems.length; i++) {
var streamType = streamItems[i].dataset.type;
var streamUrl = streamItems[i].dataset.url;
var streamName = streamItems[i].dataset.name;
streams.push({ type: streamType, url: streamUrl, name: streamName });
}
var jsonOutput = document.getElementById('jsonOutput');
var multipleStreamsInput = document.getElementById('multiple_streams_data');
jsonOutput.textContent = JSON.stringify(streams, null, 2);
multipleStreamsInput.value = JSON.stringify(streams);
}
function loadJSON() {
var multipleStreamsInput = document.getElementById('multiple_streams_data');
var jsonString = multipleStreamsInput.value;
var jsonData;
try {
jsonData = JSON.parse(jsonString);
} catch (error) {
return;
}
var streamContainer = document.getElementById('streamContainer');
streamContainer.innerHTML = '';
jsonData.forEach(function (stream) {
var streamItem = document.createElement('div');
streamItem.className = 'stream-item';
streamItem.dataset.type = stream.type;
streamItem.dataset.url = stream.url;
streamItem.dataset.name = stream.name;
streamItem.innerHTML = `
<div>
<strong>Type:</strong> ${stream.type}, <strong>URL:</strong> ${stream.url}, <strong>Name:</strong> ${stream.name}
</div>
<span class="remove-btn">Remove</span>
`;
streamContainer.appendChild(streamItem);
});
// Показать JSON Output
document.getElementById('json-output').style.display = 'block';
// Обновить JSON Output
updateJSONOutput();
}
});
</script>
<style>
.multiple-streams label {
display: block;
margin-bottom: 5px;
}
.multiple-streams input, .multiple-streams button {
margin-bottom: 10px;
}
.multiple-streams .stream-container {
margin-top: 20px;
}
.multiple-streams .stream-item {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.multiple-streams .remove-btn {
cursor: pointer;
color: red;
}
.multiple-streams #json-output {
margin-top: 20px;
}
</style>
Визуальная часть готова, но теперь необходимо сделать так, чтобы при сохранении поста, мета поле так же сохранялось. По этому добавляем следуюзщй код в метод save
// Check if our nonce is set.
if (!isset($_POST['multiple_streams_data'])) {
return $post_id;
}
$nonce = $_POST['multiple_streams_box_nonce'];
// Verify that the nonce is valid.
if (!wp_verify_nonce($nonce, 'multiple_streams_box')) {
return $post_id;
}
/*
* If this is an autosave, our form has not been submitted,
* so we don't want to do anything.
*/
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
// Check the user's permissions.
if (!current_user_can('edit_page', $post_id)) {
return $post_id;
}
/* OK, it's safe for us to save the data now. */
// Update the meta field.
update_post_meta($post_id, 'multiple_streams', $_POST['multiple_streams_data']);
Вот и класс готов, теперь нужно добавить в файл functions.php следующую строку, чтобы все заработало.
require get_template_directory() . '/inc/multiple-streams-meta-box.php';
А чтобы использовать эти самые повторяющиеся мета поля, можно достать следующим образом
$multipleStreamsArray = get_post_meta(get_the_ID(), 'multiple_streams', true);
$multipleStreamsArray = $multipleStreamsArray ? json_decode($multipleStreamsArray, true) : [];
Таким образом мы получаем массив данных с повторяющимеся полями. На самом деле, можно и воспользоватся плагинами, где это уже реализовано, но хорошие плагины часто просят премиум подписку за такую услугу, а плохие часто тормозят сайт.
Если у вас есть мысли или советы по оптимизации, всегда рад почитать в комментариях!