From 0113d2e9c9ff34a6ffb10707cc76d34b67a0c367 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 19 Jan 2026 16:29:41 +0000
Subject: [PATCH] =Refactored window.getTemplate into a full templating class window.jvbTemplates. Refactored CRUD.js, UploadManager.js, FormController.js, PopulateForm.js with that in mind

---
 inc/meta/MetaForm.php |  361 +++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 277 insertions(+), 84 deletions(-)

diff --git a/inc/meta/MetaForm.php b/inc/meta/MetaForm.php
index cf1e401..5225f9b 100644
--- a/inc/meta/MetaForm.php
+++ b/inc/meta/MetaForm.php
@@ -202,10 +202,17 @@
 		$validationAttrs = $this->buildValidationAttributes($field);
 		$conditional = array_key_exists('condition', $field) ? $this->handleConditionalField($field) : '';
 
+		$customData = '';
+		if (array_key_exists('data', $field) && !empty($field['data'])) {
+			foreach ($field['data'] as $key => $v) {
+				$customData .= ($v === '') ? ' data-' . $key : ' data-' . $key . '="' . $v . '"';
+			}
+		}
 		?>
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<?php $this->renderLabel($name, $field); ?>
@@ -217,6 +224,7 @@
 					name="<?= esc_attr($data['name']) ?>"
 					value="<?= esc_attr($data['value']) ?>"
 					<?= $inputAttrs ?>
+					<?= $customData?>
 				>
 				<span class="validation-icon success" hidden aria-hidden="true">
                     <?= jvbIcon('check-circle') ?>
@@ -359,6 +367,7 @@
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<?php $this->renderLabel($name, $field); ?>
@@ -417,6 +426,7 @@
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?> row"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<?php $this->renderLabel($name, $field); ?>
@@ -426,7 +436,7 @@
 						class="decrease"
 						title="<?= array_key_exists('remove', $field) ? $field['remove'] : 'Decrease amount' ?>"
 						aria-label="Decrease <?= esc_attr($field['label']) ?>">
-					<?= jvbIcon('minus') ?>
+					<?= jvbIcon('minus-square') ?>
 				</button>
 
 				<input type="number"
@@ -445,7 +455,7 @@
 						class="increase"
 						title="<?= array_key_exists('add', $field) ? $field['add'] : 'Increase amount' ?>"
 						aria-label="Increase <?= esc_attr($field['label']) ?>">
-					<?= jvbIcon('add') ?>
+					<?= jvbIcon('plus-square') ?>
 				</button>
 			</div>
 
@@ -467,6 +477,7 @@
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<?php $this->renderLabel($name, $field); ?>
@@ -475,8 +486,7 @@
 				<select
 					id="<?= esc_attr($data['id']) ?>"
 					name="<?= esc_attr($data['name']) ?>"
-					<?= $inputAttrs ?>
-				>
+					<?= $inputAttrs ?>>
 					<?php foreach ($field['options'] as $key => $label) : ?>
 						<option value="<?= esc_attr($key) ?>" <?php selected($value, $key); ?>>
 							<?= esc_html($label) ?>
@@ -508,6 +518,7 @@
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<fieldset>
@@ -553,6 +564,7 @@
 		<div class="field <?= esc_attr($field['type']) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<fieldset>
@@ -594,6 +606,7 @@
 		<div class="field true-false <?= esc_attr($name) ?> row btw"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>>
 
 			<label class="toggle-switch row" <?= $describedBy ?>>
@@ -624,7 +637,6 @@
 
 	private function renderRepeaterField(string $name, mixed $value, array $field):void
 	{
-		error_log('Rendering Repeater Field!');
 		$values = is_array($value) ? $value : array();
 
 		$conditional = $this->handleConditionalField($field);
@@ -637,6 +649,7 @@
 		?>
 		<div class="field repeater <?=$name?>"
 			 data-field="<?= esc_attr($name); ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $describedBy ?>
 			<?= $row_label ? 'data-label="' . esc_attr($row_label) . '"' : ''; ?>
 			<?=$conditional?>>
@@ -658,7 +671,7 @@
 				?>
 			</div>
 
-			<template class="<?=uniqid('repeaterTemplate')?>">
+			<template class="<?=uniqid('repeaterRow')?>">
 				<?php $this->renderRepeaterRow($field['fields'], array(), '', '', $rowTitle); ?>
 			</template>
 
@@ -678,11 +691,11 @@
 		<div class="repeater-row" data-index="<?= esc_attr($index); ?>">
 			<details <?= (is_string($index)) ? 'open' : ''; ?>>
 				<summary class="repeater-row-header row btw">
-					<span class="drag-handle"><?= jvbIcon('grab'); ?></span>
+					<span class="drag-handle"><?= jvbIcon('dots-six-vertical'); ?></span>
 					<span class="row-number">#<?= esc_html($display_number); ?></span>
 					<span class="row-title"><?= esc_html($this->getRowTitle($fields, $values, $rowTitle)); ?></span>
 					<button type="button" class="remove-row" title="Remove">
-						<?= jvbIcon('delete', ['title'=>'Remove']); ?>
+						<?= jvbIcon('trash', ['title'=>'Remove']); ?>
 					</button>
 				</summary>
 				<div class="repeater-row-content">
@@ -722,9 +735,11 @@
 	protected function renderGroupField(string $name, mixed $value, array $field): void
 	{
 		if (!array_key_exists('fields', $field) || empty($field['fields'])) {
+			error_log('No fields to render');
 			return;
 		}
 
+
 		$values = is_array($value) ? $value : [];
 		$original = $name;
 
@@ -744,14 +759,16 @@
 		$conditional = $this->handleConditionalField($field);
 		$validationAttrs = $this->buildValidationAttributes($field);
 		$describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : '';
-
+		$fieldset = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'details' : 'fieldset';
+		$legend = (array_key_exists('wrap', $field) && $field['wrap'] === 'details') ? 'summary' : 'legend';
 		?>
-		<fieldset class="field group <?= esc_attr($name) ?>"
+		<<?= $fieldset?> class="field group <?= esc_attr($name) ?>"
 			<?= $conditional ?>
-				  data-field="<?= esc_attr($name) ?>"
+		  	data-field="<?= esc_attr($name) ?>"
+			data-field-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>
 			<?= $describedBy ?>>
-			<legend><?= esc_html($field['label']) ?></legend>
+			<<?=$legend?>><?= esc_html($field['label']) ?></<?=$legend?>>
 
 			<?php $this->renderHintAndDescription($field, $name); ?>
 
@@ -760,7 +777,7 @@
 			</div>
 
 			<span class="validation-message" hidden role="alert"></span>
-		</fieldset>
+		</<?= $fieldset?>>
 		<?php
 	}
 
@@ -810,7 +827,7 @@
 			//Processing Options
 			'max_size' => null, // Override default size limits
 			'convert' => 'webp', // Image conversion format
-			'quality' => 80, // Conversion quality
+			'quality' => 90, // Conversion quality
 			'create_thumbnails' => true,
 		];
 		$config = array_merge($defaultConfig, $field);
@@ -872,6 +889,8 @@
 		}
 		?>
 		<div class="field upload <?= esc_attr($name) ?>"
+			 data-field="<?=esc_attr($name)?>"
+			 data-field-type="upload"
 			<?= $dataAttrString ?>
 			<?= $conditional ?>>
 
@@ -909,6 +928,7 @@
 					<?php endif; ?>
 					<div class="file-error"></div>
 				</div>
+				<?php jvbRenderProgressBar(); ?>
 			</div>
 
 
@@ -919,7 +939,7 @@
 						<div class="selection-controls">
 							<div class="selected">
 								<div class="field">
-									<input type="checkbox" id="select-all-uploads" name="select-all-uploads">
+									<input type="checkbox" id="select-all-uploads" data-select-all data-selects="item-grid" name="select-all-uploads">
 									<label for="select-all-uploads">
 										Select All
 									</label>
@@ -931,18 +951,18 @@
 
 							<div class="selection-actions row btw" hidden>
 								<button type="button" data-action="add-to-group">
-									<?= jvbIcon('add') ?>
+									<?= jvbIcon('plus-square') ?>
 									Group
 								</button>
 								<button type="button" data-action="delete-upload">
-									<?= jvbIcon('delete') ?>
+									<?= jvbIcon('trash') ?>
 									Delete
 								</button>
 							</div>
 						</div>
 
 						<button type="button" data-action="upload" class="submit-uploads">
-							<?= jvbIcon('upload') ?> Upload <?= esc_html($plural ?? 'Content'); ?>
+							<?= jvbIcon('cloud-arrow-up') ?> Upload <?= esc_html($plural ?? 'Content'); ?>
 						</button>
 					</div>
 					<?php endif; ?>
@@ -959,7 +979,7 @@
 					</div>
 
 					<?php if ($destination === 'post_group') : ?>
-					<p class="hint"><?= jvbIcon('elbow-left-up') ?>  These will become individual <?= $plural ?>  <?= jvbIcon('elbow-right-up')?></p>
+					<p class="hint"><?= jvbIcon('arrow-elbow-left-up') ?>  These will become individual <?= $plural ?>  <?= jvbIcon('arrow-elbow-right-up')?></p>
 				</div>
 				<div class="sidebar flex col">
 					<div class="header">
@@ -971,7 +991,7 @@
 							<p>Drag here to create a new <?= $singular ?>!</p>
 						</div>
 					</div>
-					<p class="hint"><?= jvbIcon('elbow-left-up') ?>  Each group will become its own <?= $singular ?>  <?= jvbIcon('elbow-right-up')?></p>
+					<p class="hint"><?= jvbIcon('arrow-elbow-left-up') ?>  Each group will become its own <?= $singular ?>  <?= jvbIcon('arrow-elbow-right-up')?></p>
 				</div>
 			</div>
 		<?php endif; ?>
@@ -986,6 +1006,29 @@
 		<?php
 	}
 
+	private function renderExistingAttachment(int $attachmentId, string $subtype): string
+	{
+		ob_start();
+
+		switch ($subtype) {
+			case 'image':
+				$this->renderImagePreview($attachmentId);
+				break;
+			case 'video':
+				$this->renderVideoPreview($attachmentId);
+				break;
+			case 'document':
+			case 'file':
+				$this->renderFilePreview($attachmentId);
+				break;
+			default:
+				$this->renderImagePreview($attachmentId);
+				break;
+		}
+
+		return ob_get_clean();
+	}
+
 	/**
 	 * Get max file size for subtype
 	 */
@@ -1068,68 +1111,72 @@
 		$dataID = ($id) ? ['id' => $id] : '';
 		?>
 		<div class="item upload"<?= ($id) ? ' data-id="'.$id.'"' : '' ?>>
-				<div class="preview">
-					<?php jvbRenderProgressBar('',true) ?>
-					<input type="checkbox" class="upload-select" name="select-item" id="select-item<?=$addID?>">
-					<label for="select-item<?=$addID?>" aria-label="Select image">
-						<?= ($attachment) ? $attachment : '<img>
-						<video></video>
-						<span></span>' ?>
-					</label>
-					<div class="item-actions row btw">
-						<div class="radio-button">
-							<input type="radio" class="featured btn" name="featured" id="featured" hidden>
-							<label for="featured">
-								<?=jvbIcon('star')?>
-								<?=jvbIcon('star', ['style' => 'fill'])?>
-								<span class="screen-reader-text">Set as featured image</span>
-							</label>
-						</div>
-
-						<button type="button" data-action="delete-upload" title="Remove from Group">
-							<?=jvbIcon('delete')?>
-						</button>
+			<div class="preview">
+				<?php jvbRenderProgressBar('',true) ?>
+				<input type="checkbox" class="upload-select" name="select-item" id="select-item<?=$addID?>">
+				<label for="select-item<?=$addID?>" aria-label="Select image">
+					<?= ($attachment) ?: '<img>
+                <video></video>
+                <span></span>' ?>
+				</label>
+				<div class="item-actions row btw">
+					<div class="radio-button">
+						<input type="radio" class="featured btn" name="featured" id="featured" hidden>
+						<label for="featured">
+							<?=jvbIcon('star')?>
+							<?=jvbIcon('star', ['style' => 'fill'])?>
+							<span class="screen-reader-text">Set as featured image</span>
+						</label>
 					</div>
+
+					<button type="button" data-action="delete-upload" title="Remove from Group">
+						<?=jvbIcon('trash')?>
+					</button>
 				</div>
-				<details>';
-					<summary class="row btw"><?=jvbIcon('edit')?><span>Edit Info</span></summary>
+			</div>
+			<details>
+				<summary class="row btw"><?=jvbIcon('pencil-simple')?><span>Edit Info</span></summary>
 
-		<?php
+				<?php
+				$fields = array_key_exists('fields', $config) ? $config['fields'] : [];
 
-		$fields = array_key_exists('fields', $config) ? $config['fields'] : [];
-		$fields = array_merge([
-			'upload_data'	=> [
-				'type'	=> 'group',
-				'wrap'	=> 'details',
-				'label'	=> 'Image Info',
-				'hint'	=> 'These will be automatically generated if left blank.',
-				'fields'	=> [
-					'image-title'.$addID => [
-						'type'	=> 'text',
-						'label'	=> 'Image Title',
-						'value'	=> $title,
-						'data'	=> $dataID
-					],
-					'image-alt-text'.$addID => [
-						'type'	=> 'text',
-						'label'	=> 'Alt Text',
-						'value'	=> $alt,
-						'hint'	=> 'Alt text helps the visually impaired, as well as some benefits for SEO.',
-						'data'	=> $dataID
-					],
-					'image-caption'.$addID => [
-						'type'	=> 'textarea',
-						'value'	=> $caption,
-						'label'	=> 'Image Caption',
-						'data'	=> $dataID
-					]
-				]
-			]
-		], $fields);
+				// Only add image_data if not already provided
+				if (!array_key_exists('image_data', $fields)) {
+					$fields['image_data'] = [
+						'type'   => 'group',
+						'wrap'   => 'details',
+						'label'  => 'Image Info',
+						'hint'   => 'These will be automatically generated if left blank.',
+						'fields' => [
+							'image-title'.$addID => [
+								'type'  => 'text',
+								'label' => 'Image Title',
+								'value' => $title,
+								'data'  => $dataID
+							],
+							'image-alt-text'.$addID => [
+								'type'  => 'text',
+								'label' => 'Alt Text',
+								'value' => $alt,
+								'hint'  => 'Alt text helps the visually impaired, as well as some benefits for SEO.',
+								'data'  => $dataID
+							],
+							'image-caption'.$addID => [
+								'type'  => 'textarea',
+								'value' => $caption,
+								'label' => 'Image Caption',
+								'data'  => $dataID
+							]
+						]
+					];
+				}
 
-		$this->render('upload_data', null, $fields);
-		?>
-				</details>
+				$meta = new MetaManager($id);
+				foreach ($fields as $field => $config) {
+					$meta->render('form', $field, $config);
+				}
+				?>
+			</details>
 		</div>
 		<?php
 	}
@@ -1162,12 +1209,12 @@
 						</div>
 
 						<button type="button" data-action="delete-upload" title="Remove from Group">
-							<?=jvbIcon('delete')?>
+							<?=jvbIcon('trash')?>
 						</button>
 					</div>
 				</div>
 				<details>';
-					<summary class="row btw"><?=jvbIcon('edit')?><span>Edit Info</span></summary>
+					<summary class="row btw"><?=jvbIcon('pencil-simple')?><span>Edit Info</span></summary>
 
 		<?php
 		$fields = array_key_exists('fields', $config) ? $config['fields'] : [];
@@ -1235,12 +1282,12 @@
 						</div>
 
 						<button type="button" data-action="delete-upload" title="Remove from Group">
-							<?=jvbIcon('delete')?>
+							<?=jvbIcon('trash')?>
 						</button>
 					</div>
 				</div>
 				<details>';
-					<summary class="row btw"><?=jvbIcon('edit')?><span>Edit Info</span></summary>
+					<summary class="row btw"><?=jvbIcon('pencil-simple')?><span>Edit Info</span></summary>
 
 		<?php
 		$fields = array_key_exists('fields', $config) ? $config['fields'] : [];
@@ -1316,13 +1363,12 @@
 	 * Generic selector field renderer
 	 * Handles both taxonomy and post selectors with consistent structure
 	 */
-	private function renderSelectorField(string $name, mixed $value, array $field, string $type): void
+	public function renderSelectorField(string $name, mixed $value, array $field, string $type): void
 	{
 		$conditional = $this->handleConditionalField($field);
 		$validationAttrs = $this->buildValidationAttributes($field);
 		$describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : '';
 
-		$isSimple = (array_key_exists('mode', $field) && $field['mode']==='simple');
 		// Parse selected values
 		$value = (is_array($value)) ? array_filter(array_map('absint', $value)): $value;
 		$selected = ($value === '') ? [] : (is_array($value) ? $value : explode(',', $value));
@@ -1373,9 +1419,11 @@
 		}
 
 		?>
-		<div class="field <?= esc_attr($type) ?> <?= esc_attr($name) ?>"
+		<div class="field selector <?= esc_attr($type) ?> <?= esc_attr($name) ?>"
 			<?= $conditional ?>
 			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="selector"
+			 data-type="<?=esc_attr($field['type'])?>"
 			<?= $validationAttrs ?>
 			<?= $describedBy ?>>
 
@@ -1441,6 +1489,7 @@
 
 		<div class="field location <?= esc_attr($field_id) ?>"
 			 data-field="<?= esc_attr($field_id) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
 			 data-location-field-init="<?= $json_config ?>"<?=$describedBy?>>
 
 			<?php
@@ -1583,5 +1632,149 @@
 
 		return is_numeric($value) ? [$value] : [];
 	}
+	/**
+	 * Render tag list field - inline tag input interface
+	 */
+	protected function renderTagListField(string $name, mixed $value, array $field): void
+	{
+		$values = is_array($value) ? $value : [];
+		$conditional = $this->handleConditionalField($field);
+		$validationAttrs = $this->buildValidationAttributes($field);
 
+		if (array_key_exists('group', $field)) {
+			$name = $field['group'] . '::' . $name;
+		}
+
+		$describedBy = (!empty($field['description'])) ? ' aria-describedby="' . $name . '-help"' : '';
+
+		// Tag display format - defaults to first field value
+		$tagFormat = $field['tag_format'] ?? 'first_field';
+		?>
+		<div class="field tag-list <?= esc_attr($name) ?>"
+			 data-field="<?= esc_attr($name) ?>"
+			 data-field-type="<?=esc_attr($field['type'])?>"
+			 data-tag-format="<?= esc_attr($tagFormat) ?>"
+			<?= $describedBy ?>
+			<?= $conditional ?>
+			<?= $validationAttrs ?>>
+
+			<?php if (!empty($field['label'])): ?>
+				<h3><?= esc_html($field['label']) ?></h3>
+			<?php endif; ?>
+
+			<!-- Inline input row -->
+			<div class="tag-input-row">
+				<?php foreach ($field['fields'] as $subfield_name => $subfield_config): ?>
+					<?php
+					$subfield_config['label'] = $subfield_config['label'] ?? ucfirst($subfield_name);
+					$input_name = 'new_' . $subfield_name;
+
+					// Store required state but don't render it on the input
+					// This prevents form submission validation but allows JS validation
+
+					if (array_key_exists('required', $subfield_config)) {
+						$subfield_config['data']['required'] = true;
+						unset($subfield_config['required']); // Remove required for HTML rendering
+					}
+					$subfield_config['data']['ignore'] = true;
+
+					$this->render($input_name, '', $subfield_config, false, false);
+					?>
+				<?php endforeach; ?>
+
+				<button type="button" class="button add-tag-item">
+					<?= jvbIcon('plus') ?> <?= $field['add_label'] ?? 'Add' ?>
+				</button>
+			</div>
+
+			<!-- Tags display -->
+			<div class="tag-items">
+				<?php foreach ($values as $index => $item_data): ?>
+					<?php $this->renderTagItem($field['fields'], $item_data, $index, $name, $tagFormat); ?>
+				<?php endforeach; ?>
+			</div>
+
+			<!-- Template for new tags -->
+			<template class="<?=uniqid('tagListItem')?>">
+				<?php $this->renderTagItem($field['fields'], [], '', $name, $tagFormat); ?>
+			</template>
+
+			<?php if (!empty($field['hint'])): ?>
+				<?php $this->renderHint($field['hint']); ?>
+			<?php endif; ?>
+
+			<?php if (!empty($field['description'])): ?>
+				<?php $this->renderDescription($field['description'], $name); ?>
+			<?php endif; ?>
+		</div>
+		<?php
+	}
+
+	/**
+	 * Render individual tag item
+	 */
+	protected function renderTagItem(array $fields, array $data, int|string $index, string $base_name, string $format): void
+	{
+		$tag_text = $this->getTagDisplayText($fields, $data, $format);
+		?>
+		<div class="tag-item" data-index="<?= esc_attr($index) ?>">
+			<span class="tag-label"><?= esc_html($tag_text) ?></span>
+
+			<!-- Hidden inputs for data -->
+			<?php foreach ($fields as $field_name => $field_config): ?>
+				<?php
+				$value = $data[$field_name] ?? '';
+				$full_name = is_string($index) ? $field_name : "{$base_name}:{$index}:{$field_name}";
+				?>
+				<input type="hidden"
+					   name="<?= esc_attr($full_name) ?>"
+					   value="<?= esc_attr($value) ?>"
+					   data-field="<?= esc_attr($field_name) ?>"
+					   data-field-type="<?=esc_attr($field_config['type'])?>" />
+			<?php endforeach; ?>
+
+			<button type="button" class="remove-tag" aria-label="Remove">
+				<?= jvbIcon('x') ?>
+			</button>
+		</div>
+		<?php
+	}
+
+	/**
+	 * Get tag display text based on format
+	 */
+	protected function getTagDisplayText(array $fields, array $data, string $format): string
+	{
+		if (empty($data)) {
+			return 'New Item';
+		}
+
+		switch ($format) {
+			case 'first_field':
+				// Use the first field's value
+				$first_key = array_key_first($fields);
+				return $data[$first_key] ?? 'New Item';
+
+			case 'all_fields':
+				// Show all field values separated by commas
+				$values = array_filter(array_values($data));
+				return implode(', ', $values) ?: 'New Item';
+
+			case 'custom':
+				// Custom format - would need callback
+				return 'New Item';
+
+			default:
+				// Format is a template string like "{name} ({email})"
+				if (strpos($format, '{') !== false) {
+					$text = $format;
+					foreach ($data as $key => $value) {
+						$text = str_replace('{' . $key . '}', $value, $text);
+					}
+					return $text;
+				}
+				// Use specific field name
+				return $data[$format] ?? 'New Item';
+		}
+	}
 }

--
Gitblit v1.10.0