From 58e8ae0759ccfa97c478ccae4e0778bdce70966f Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 22 Jan 2026 22:40:02 +0000
Subject: [PATCH] =DirectoryManager.php updates, some javascript tweaks for CRUD.js, and minor style changes
---
inc/meta/MetaForm.php | 355 +++++++++++++++++++++++++++++++++++++++++++++-------------
1 files changed, 273 insertions(+), 82 deletions(-)
diff --git a/inc/meta/MetaForm.php b/inc/meta/MetaForm.php
index 21895b9..ea8f614 100644
--- a/inc/meta/MetaForm.php
+++ b/inc/meta/MetaForm.php
@@ -202,10 +202,18 @@
$validationAttrs = $this->buildValidationAttributes($field);
$conditional = array_key_exists('condition', $field) ? $this->handleConditionalField($field) : '';
+ $pattern = array_key_exists('pattern', $field) ? $field['pattern'] : '';
+ $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 +225,8 @@
name="<?= esc_attr($data['name']) ?>"
value="<?= esc_attr($data['value']) ?>"
<?= $inputAttrs ?>
+ <?= $customData?>
+ <?= $pattern?>
>
<span class="validation-icon success" hidden aria-hidden="true">
<?= jvbIcon('check-circle') ?>
@@ -359,6 +369,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 +428,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); ?>
@@ -467,6 +479,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 +488,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 +520,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 +566,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 +608,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 +639,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 +651,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 +673,7 @@
?>
</div>
- <template class="<?=uniqid('repeaterTemplate')?>">
+ <template class="<?=uniqid('repeaterRow')?>">
<?php $this->renderRepeaterRow($field['fields'], array(), '', '', $rowTitle); ?>
</template>
@@ -722,9 +737,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 +761,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 +779,7 @@
</div>
<span class="validation-message" hidden role="alert"></span>
- </fieldset>
+ </<?= $fieldset?>>
<?php
}
@@ -772,7 +791,9 @@
{
foreach ($field['fields'] as $field_name => $config) {
// Set the group context for proper field naming
- $config['group'] = $groupName;
+ if (!array_key_exists('wrap', $field) || $field['wrap'] !== 'details') {
+ $config['group'] = $groupName;
+ }
// Get the value for this specific field
$field_value = $values[$field_name] ?? '';
@@ -810,7 +831,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);
@@ -826,7 +847,7 @@
// Build accept attribute for input
$acceptExtensions = $this->getMimeExtensions($acceptedTypes);
- $acceptAttr = implode(',', $acceptExtensions);
+ $acceptAttr = implode(',', $acceptedTypes);
// Determine field attributes
$subtype = $config['subtype'] ?? 'image';
@@ -872,6 +893,8 @@
}
?>
<div class="field upload <?= esc_attr($name) ?>"
+ data-field="<?=esc_attr($name)?>"
+ data-field-type="upload"
<?= $dataAttrString ?>
<?= $conditional ?>>
@@ -909,6 +932,7 @@
<?php endif; ?>
<div class="file-error"></div>
</div>
+ <?php jvbRenderProgressBar(); ?>
</div>
@@ -919,7 +943,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>
@@ -986,6 +1010,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,71 +1115,69 @@
$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('trash')?>
- </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('pencil-simple')?><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_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 = [
+ 'image_data' => [
+ 'type' => 'group',
+ 'wrap' => 'details',
+ 'label' => 'Image Fields',
+ '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);
-
- $meta = new MetaManager($id);
- foreach ($fields as $field => $config) {
- $meta->render('form', $field, $config);
- }
- ?>
- </details>
+ ];
+ $fields = array_key_exists('fields', $config) ? array_merge($fields, $config['fields']) : $fields;
+ $meta = new MetaManager($id);
+ foreach ($fields as $field => $config) {
+ $meta->render('form', $field, $config);
+ }
+ ?>
+ </details>
</div>
<?php
}
@@ -1150,7 +1195,7 @@
<?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>
+ <?= ($attachment) ?: '<img>
<video></video>
<span></span>'; ?>
</label>
@@ -1223,7 +1268,7 @@
<?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>
+ <?= ($attachment) ?: '<img>
<video></video>
<span></span>'; ?>
</label>
@@ -1319,13 +1364,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));
@@ -1376,9 +1420,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 ?>>
@@ -1444,6 +1490,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
@@ -1548,11 +1595,11 @@
// Default types based on subtype
$defaults = [
- 'image' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
- 'video' => ['video/mp4', 'video/webm', 'video/ogg'],
- 'document' => ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
- 'any' => ['image/*', 'video/*', 'application/pdf']
+ 'image' => ['image/*'],
+ 'video' => ['video/*'],
+ 'document' => ['application/pdf', 'application/msword', 'application/vnd.ms-excel', 'text/plain', '.odt','application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
];
+ $defaults['any'] = array_merge($defaults['image'], $defaults['video'], $defaults['document']);
return $defaults[$config['subtype']] ?? $defaults['image'];
}
@@ -1586,5 +1633,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