wordpress-repeatable-meta-fields

Wordpress

Как сделать повторяющиеся мета поля в WordPress (repeater)

• 23-12-2023 •

Не часто, но иногда необходимо, когда на сайт требуется добавить несколько однотипной информации. К примеру в моем случае, на одном из моих сайтов, а сайт, это каталог прямых трансляций с веб-камер, добавлять на одну страницу несколько стримов, где бы они выводились в виде кнопок и можно было бы переключатся между ними.

И для одного стрима нужно указать тип, название и ссылку потока. И так как стримов может быть бесконечное количество, нужно было реализовать повторяющие мета поля или еще их называют repeatable meta fields. Идея была такая, мы создаем объект и заполняем его данными и преваращаем в json строку, которые помещаем в input поле.

В итоге вышло вот так вот:

meta-fields-repeatable

Как это выглядит вживую можно посмотреть тут: Краснодар, панорама — Веб-камеры. А как это реализовать читаем ниже.

Создание класса

Создаем отдельный файл, я разместил в папке ./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) : [];

Таким образом мы получаем массив данных с повторяющимеся полями. На самом деле, можно и воспользоватся плагинами, где это уже реализовано, но хорошие плагины часто просят премиум подписку за такую услугу, а плохие часто тормозят сайт.

Если у вас есть мысли или советы по оптимизации, всегда рад почитать в комментариях!

Добавить комментарий