Skip to content

Instantly share code, notes, and snippets.

@Qubadi
Last active August 26, 2025 17:07
Show Gist options
  • Save Qubadi/142c8637b06c936c0e21846488611440 to your computer and use it in GitHub Desktop.
Save Qubadi/142c8637b06c936c0e21846488611440 to your computer and use it in GitHub Desktop.
JetFormbuilder Choose file ( mediafield ) + drag and drop + progressbar + image compression
UPDATED: 19.08.2025
Copy the following PHP and create a PHP snippet using your snippet plugins.
Paste the code into the plugin and save it.
Short description:
• A new design for choose file with new style, where everything can be controlled in backend.
• Added a new true network progress bar, when new images are uploaded.
• We have added a new option for image compression, where you can easily toggle on or off in backend.
(Note: Image compression works only on desktop and not on phone when you try to upload an image + image compression)
• We have added Target CSS Class where you add a CSS classname, and copy the same CSS classname and add it to the form
you have created in JetFormBuilder for media field → Block settings → Advanced. Where it shows CSS classname, add the
classname there and save your form. This way the new style for choose file only targets this form if you have more than one form.
• The new style is fully responsive, and over 90% can be redesigned in backend where the JFB Media Field shows in WordPress Dashboard.
• Convert image is a new future request.
____________________________________________________
/** ----------------------------------------------------------------
* Helpers: defaults + options
* ---------------------------------------------------------------- */
function jfbm_defaults() {
return [
'target_css_class' => 'media_image',
'upload_style' => 'drag_drop',
'button_bg_color' => '#8b5cf6',
'button_text_color' => '#ffffff',
'button_alignment' => 'left',
'upload_text' => 'Click to Upload',
'upload_text_color' => '#8b5cf6',
'sub_text' => 'or drag and drop',
'sub_text_color' => '#64748b',
'box_padding' => '40px 20px',
'box_margin' => '0 0 1rem 0',
'box_border_color' => '#ddd',
'box_border_radius' => '12px',
'primary_color' => '#8b5cf6',
'progress_color' => '#8b5cf6',
'item_bg_color' => '#f8fafc',
'file_text_color' => '#334155',
'file_text_weight' => '500',
'filename_trim_length' => 20,
'remove_icon_color' => '#cbd5e1',
'remove_icon_size' => '18px',
'upload_status_text' => '{count} file(s) uploading...',
'total_status_text' => '{count} file(s) uploaded',
'scroll_threshold' => 6,
'max_files_error' => 'You can only upload a maximum of {max} file(s)',
'max_size_error' => 'File "{name}" exceeds the maximum allowed size of {size}',
'file_type_error' => 'File type "{type}" is not allowed',
'upload_failed_error' => 'Upload failed. Please try again.',
// Error styling
'error_bg_color' => '#fecaca',
'error_text_color' => '#ffffff',
'error_font_size' => '14px',
'error_border_radius' => '6px',
'error_padding' => '12px 16px',
'error_auto_hide_duration'=> 4,
// Compression
'enable_compression' => 0,
'max_width' => 4096,
'max_height' => 4096,
'quality' => 85,
'keep_original_dimensions'=> 0,// ✅ NEW: when 1, never resize, only re-encode using 'keep_original_dimensions'=> 0,//
'drag_over_bg_color' => '#f5f3ff',
'drag_over_border_color' => '#8b5cf6',
];
}
function jfbm_opts() {
return array_merge(jfbm_defaults(), (array) get_option('jfbm_settings', []));
}
/** ----------------------------------------------------------------
* Admin: menu + settings
* ---------------------------------------------------------------- */
add_action('admin_menu', function () {
add_menu_page(
'JFB Media Field',
'JFB Media Field',
'manage_options',
'jfbm-settings',
'jfbm_settings_page',
'dashicons-upload',
80
);
});
add_action('admin_init', function () {
register_setting('jfbm_settings_group', 'jfbm_settings');
});
function jfbm_settings_page() {
$opt = jfbm_opts();
?>
<div class="wrap" id="jfbm-settings-page">
<h1 class="jfbm-page-title">JFB Media Field Styling</h1>
<form method="post" action="options.php">
<?php settings_fields('jfbm_settings_group'); ?>
<h2>Design</h2>
<table class="form-table">
<tr><th>Target CSS Class</th>
<td>
<input type="text" name="jfbm_settings[target_css_class]" value="<?php echo esc_attr($opt['target_css_class']); ?>" class="regular-text" />
<br><small>Only apply styling to media fields with this CSS class name (e.g., "media_image")</small>
</td>
</tr>
<tr><th>Upload Style</th>
<td>
<div class="jfbm-radio-group">
<label class="jfbm-radio">
<input type="radio" name="jfbm_settings[upload_style]" value="drag_drop" <?php checked($opt['upload_style'], 'drag_drop'); ?>>
<span class="jfbm-radio-mark"></span>
<span>Drag and Drop (Default)</span>
</label>
<label class="jfbm-radio">
<input type="radio" name="jfbm_settings[upload_style]" value="button" <?php checked($opt['upload_style'], 'button'); ?>>
<span class="jfbm-radio-mark"></span>
<span>Choose File Button Only</span>
</label>
</div>
</td>
</tr>
<tr><th>Button Background Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[button_bg_color]" value="<?php echo esc_attr($opt['button_bg_color']); ?>">
<code><?php echo esc_html($opt['button_bg_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Button Text Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[button_text_color]" value="<?php echo esc_attr($opt['button_text_color']); ?>">
<code><?php echo esc_html($opt['button_text_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Button Alignment</th>
<td>
<select name="jfbm_settings[button_alignment]" class="regular-text">
<option value="left" <?php selected($opt['button_alignment'], 'left'); ?>>Left</option>
<option value="center" <?php selected($opt['button_alignment'], 'center'); ?>>Center</option>
<option value="right" <?php selected($opt['button_alignment'], 'right'); ?>>Right</option>
</select>
</td>
</tr>
<tr><th>Upload Text</th>
<td><input type="text" name="jfbm_settings[upload_text]" value="<?php echo esc_attr($opt['upload_text']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Upload Text Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[upload_text_color]" value="<?php echo esc_attr($opt['upload_text_color']); ?>">
<code><?php echo esc_html($opt['upload_text_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Error Background Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[error_bg_color]" value="<?php echo esc_attr($opt['error_bg_color']); ?>">
<code><?php echo esc_html($opt['error_bg_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Error Text Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[error_text_color]" value="<?php echo esc_attr($opt['error_text_color']); ?>">
<code><?php echo esc_html($opt['error_text_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Box Padding</th>
<td><input type="text" name="jfbm_settings[box_padding]" value="<?php echo esc_attr($opt['box_padding']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Box Margin</th>
<td><input type="text" name="jfbm_settings[box_margin]" value="<?php echo esc_attr($opt['box_margin']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Box Border Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[box_border_color]" value="<?php echo esc_attr($opt['box_border_color']); ?>">
<code><?php echo esc_html($opt['box_border_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Box Border Radius</th>
<td><input type="text" name="jfbm_settings[box_border_radius]" value="<?php echo esc_attr($opt['box_border_radius']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Progress Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[progress_color]" value="<?php echo esc_attr($opt['progress_color']); ?>">
<code><?php echo esc_html($opt['progress_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Item Background Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[item_bg_color]" value="<?php echo esc_attr($opt['item_bg_color']); ?>">
<code><?php echo esc_html($opt['item_bg_color']); ?></code>
</div>
</td>
</tr>
<tr><th>File Text Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[file_text_color]" value="<?php echo esc_attr($opt['file_text_color']); ?>">
<code><?php echo esc_html($opt['file_text_color']); ?></code>
</div>
</td>
</tr>
<tr><th>File Text Weight</th>
<td><input type="text" name="jfbm_settings[file_text_weight]" value="<?php echo esc_attr($opt['file_text_weight']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Filename Trim Length</th>
<td><input type="number" name="jfbm_settings[filename_trim_length]" value="<?php echo esc_attr($opt['filename_trim_length']); ?>" class="small-text" /> characters</td>
</tr>
<tr><th>Remove Icon Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[remove_icon_color]" value="<?php echo esc_attr($opt['remove_icon_color']); ?>">
<code><?php echo esc_html($opt['remove_icon_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Remove Icon Size</th>
<td><input type="text" name="jfbm_settings[remove_icon_size]" value="<?php echo esc_attr($opt['remove_icon_size']); ?>" class="regular-text" /></td>
</tr>
<tr><th>Scroll Threshold</th>
<td><input type="number" name="jfbm_settings[scroll_threshold]" value="<?php echo esc_attr($opt['scroll_threshold']); ?>" class="small-text" /> items</td>
</tr>
<tr><th>Drag Over Background Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[drag_over_bg_color]" value="<?php echo esc_attr($opt['drag_over_bg_color']); ?>">
<code><?php echo esc_html($opt['drag_over_bg_color']); ?></code>
</div>
</td>
</tr>
<tr><th>Drag Over Border Color</th>
<td>
<div class="jfbm-color-field">
<input type="color" name="jfbm_settings[drag_over_border_color]" value="<?php echo esc_attr($opt['drag_over_border_color']); ?>">
<code><?php echo esc_html($opt['drag_over_border_color']); ?></code>
</div>
</td>
</tr>
</table>
<h2>Translations</h2>
<table class="form-table">
<tr><th>Uploading Status Text</th>
<td>
<input type="text" name="jfbm_settings[upload_status_text]" value="<?php echo esc_attr($opt['upload_status_text']); ?>" class="regular-text" /><br>
<small>Use <code>{count}</code> for number of files.</small>
</td>
</tr>
<tr><th>Total Status Text</th>
<td>
<input type="text" name="jfbm_settings[total_status_text]" value="<?php echo esc_attr($opt['total_status_text']); ?>" class="regular-text" /><br>
<small>Use <code>{count}</code> for number of files.</small>
</td>
</tr>
</table>
<h2>Error Messages</h2>
<table class="form-table">
<tr><th>Max Files Exceeded</th>
<td>
<input type="text" name="jfbm_settings[max_files_error]" value="<?php echo esc_attr($opt['max_files_error']); ?>" class="regular-text" /><br>
<small>Use <code>{max}</code> for maximum number of files.</small>
</td>
</tr>
<tr><th>Max Size Exceeded</th>
<td>
<input type="text" name="jfbm_settings[max_size_error]" value="<?php echo esc_attr($opt['max_size_error']); ?>" class="regular-text" /><br>
<small>Use <code>{name}</code> for filename and <code>{size}</code> for max size.</small>
</td>
</tr>
<tr><th>Invalid File Type</th>
<td>
<input type="text" name="jfbm_settings[file_type_error]" value="<?php echo esc_attr($opt['file_type_error']); ?>" class="regular-text" /><br>
<small>Use <code>{type}</code> for file type/extension.</small>
</td>
</tr>
<tr><th>Upload Failed</th>
<td>
<input type="text" name="jfbm_settings[upload_failed_error]" value="<?php echo esc_attr($opt['upload_failed_error']); ?>" class="regular-text" />
</td>
</tr>
</table>
<h2>Image Compression</h2>
<table class="form-table">
<tr><th>Enable Image Compression</th>
<td>
<label class="jfbm-toggle">
<input type="checkbox" name="jfbm_settings[enable_compression]" value="1" <?php checked((int)$opt['enable_compression'], 1); ?>>
<span class="jfbm-toggle-slider"></span>
<span class="jfbm-toggle-label">Automatically compress large images before upload</span>
</label>
</td>
</tr>
<tr><th>Keep Original Dimensions</th>
<td>
<label class="jfbm-toggle">
<input type="checkbox" name="jfbm_settings[keep_original_dimensions]" value="1" <?php checked((int)$opt['keep_original_dimensions'], 1); ?>>
<span class="jfbm-toggle-slider"></span>
<span class="jfbm-toggle-label">Do not resize images (ignore Max W/H); only re-encode using JPEG quality</span>
</label>
</td>
</tr>
<tr><th>Max Image Width</th>
<td><input type="number" name="jfbm_settings[max_width]" value="<?php echo esc_attr($opt['max_width']); ?>" class="regular-text" /> pixels</td>
</tr>
<tr><th>Max Image Height</th>
<td><input type="number" name="jfbm_settings[max_height]" value="<?php echo esc_attr($opt['max_height']); ?>" class="regular-text" /> pixels</td>
</tr>
<tr><th>Compression Quality</th>
<td><input type="number" name="jfbm_settings[quality]" value="<?php echo esc_attr($opt['quality']); ?>" min="10" max="100" class="small-text" />%</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<style>
#jfbm-settings-page{max-width:1200px;margin:20px auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;--primary-color:#8b5cf6}
.jfbm-page-title{color:#23282d;font-size:28px;font-weight:500;margin-bottom:30px;padding-bottom:15px;border-bottom:4px solid var(--primary-color);display:inline-block}
#jfbm-settings-page h2{background:#f9fafb;padding:15px 20px;margin:30px 0 0;border-radius:8px 8px 0 0;font-size:18px;font-weight:500;color:#111827;border:1px solid #e5e7eb;border-bottom:none}
#jfbm-settings-page .form-table{background:white;border-radius:0 0 8px 8px;box-shadow:0 1px 3px rgba(0,0,0,0.1);margin-top:0;border:1px solid #e5e7eb;border-top:none}
#jfbm-settings-page .form-table th{padding:20px;width:200px;font-weight:500;color:#4B5563}
#jfbm-settings-page .form-table td{padding:15px 20px}
#jfbm-settings-page input[type="text"],#jfbm-settings-page input[type="number"],#jfbm-settings-page select{padding:8px 12px;border:1px solid #D1D5DB;border-radius:6px;font-size:14px;color:#111827;transition:border-color 0.2s}
#jfbm-settings-page input[type="text"]:focus,#jfbm-settings-page input[type="number"]:focus,#jfbm-settings-page select:focus{border-color:var(--primary-color);box-shadow:0 0 0 2px rgba(139,92,246,0.2);outline:none}
#jfbm-settings-page input[type="color"]{width:50px;height:50px;border:1px solid #D1D5DB;border-radius:8px;padding:3px;cursor:pointer;-webkit-appearance:none}
#jfbm-settings-page input[type="color"]::-webkit-color-swatch-wrapper{padding:0}
#jfbm-settings-page input[type="color"]::-webkit-color-swatch{border:none;border-radius:4px}
.jfbm-color-field{display:flex;align-items:center;gap:10px}
.jfbm-color-field code{background:#f3f4f6;padding:8px;border-radius:6px;font-family:monospace;font-size:14px;color:#4B5563;display:inline-block}
#jfbm-settings-page small code{background:#f3f4f6;padding:2px 5px;border-radius:4px;font-family:monospace}
#jfbm-settings-page .submit{text-align:right;margin-top:30px;padding:0}
#jfbm-settings-page .button-primary{background:var(--primary-color);border-color:#7c3aed;color:white;padding:8px 20px;height:auto;font-size:14px;font-weight:500;border-radius:6px;text-shadow:none;box-shadow:0 2px 4px rgba(0,0,0,0.05)}
#jfbm-settings-page .button-primary:hover{background:#7c3aed;border-color:#6d28d9}
#jfbm-settings-page .button-primary:focus{box-shadow:0 0 0 1px white,0 0 0 3px #a78bfa;outline:none}
.jfbm-radio-group{display:flex;gap:24px}
.jfbm-radio{display:flex;align-items:center;position:relative;cursor:pointer;padding:5px 0}
.jfbm-radio input[type="radio"]{position:absolute;opacity:0;width:0;height:0}
.jfbm-radio-mark{position:relative;display:inline-block;width:22px;height:22px;margin-right:10px;border:2px solid #d1d5db;border-radius:50%;transition:all 2s}
.jfbm-radio input[type="radio"]:checked+.jfbm-radio-mark{border-color:var(--primary-color);background-color:white}
.jfbm-radio input[type="radio"]:checked+.jfbm-radio-mark:after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:12px;height:12px;border-radius:50%;background-color:var(--primary-color)}
.jfbm-radio input[type="radio"]:focus+.jfbm-radio-mark{box-shadow:0 0 0 2px rgba(139,92,246,0.3)}
.jfbm-toggle{display:flex;align-items:center;gap:12px}
.jfbm-toggle input[type="checkbox"]{display:none}
.jfbm-toggle-slider{position:relative;width:44px;height:24px;background:#cbd5e1;border-radius:24px;transition:0.3s;cursor:pointer}
.jfbm-toggle-slider:before{content:'';position:absolute;height:18px;width:18px;left:3px;top:3px;background:white;border-radius:50%;transition:0.3s}
.jfbm-toggle input:checked+.jfbm-toggle-slider{background:var(--primary-color)}
.jfbm-toggle input:checked+.jfbm-toggle-slider:before{transform:translateX(20px)}
.jfbm-toggle-label{color:#4B5563;font-size:14px}
@media(max-width:767px){
#jfbm-settings-page .form-table,#jfbm-settings-page .form-table tbody,#jfbm-settings-page .form-table tr,#jfbm-settings-page .form-table th,#jfbm-settings-page .form-table td{display:block;width:100%}
#jfbm-settings-page .form-table th{padding-bottom:5px}
#jfbm-settings-page .form-table td{padding-top:0}
#jfbm-settings-page input[type="text"],#jfbm-settings-page select{width:100%}
.jfbm-radio-group{flex-direction:column;gap:12px}
}
</style>
<?php
}
/** ----------------------------------------------------------------
* AJAX handler (legacy temp uploader — kept for backward compat)
* ---------------------------------------------------------------- */
add_action('wp_ajax_jfbm_upload', 'jfbm_upload_handler');
add_action('wp_ajax_nopriv_jfbm_upload', 'jfbm_upload_handler');
function jfbm_upload_handler() {
// NOTE: Native JFB upload in use; this AJAX path is no longer invoked.
$nonce = isset($_REQUEST['nonce']) ? (string) $_REQUEST['nonce'] : '';
if (!wp_verify_nonce($nonce, 'jfbm_upload')) {
wp_send_json_error('Invalid nonce', 400);
}
if (empty($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
wp_send_json_error('No file', 400);
}
// Legacy temp validation (kept for backward compatibility; not used now)
$check = wp_check_filetype_and_ext($_FILES['file']['tmp_name'], $_FILES['file']['name']);
if (empty($check['ext']) || empty($check['type'])) {
wp_send_json_error('Invalid file type.', 400);
}
$uploads = wp_upload_dir(null, false);
if (!empty($uploads['error'])) {
wp_send_json_error($uploads['error'], 500);
}
$temp_dir = trailingslashit($uploads['basedir']) . 'jfbm-temp';
if (!file_exists($temp_dir) && !wp_mkdir_p($temp_dir)) {
wp_send_json_error('Cannot create temp directory.', 500);
}
$safe_name = sanitize_file_name($_FILES['file']['name']);
$filename = wp_unique_filename($temp_dir, $safe_name);
$dest_path = trailingslashit($temp_dir) . $filename;
if (!@move_uploaded_file($_FILES['file']['tmp_name'], $dest_path)) {
wp_send_json_error('Upload failed. Please try again.', 500);
}
$temp_url = trailingslashit($uploads['baseurl']) . 'jfbm-temp/' . $filename;
@chmod($dest_path, 0644);
wp_send_json_success([
'tmp' => true,
'url' => $temp_url,
'name' => $filename,
'type' => $check['type'],
'size' => (int) $_FILES['file']['size'],
]);
}
/** ----------------------------------------------------------------
* AJAX handler (probe-only; no disk write, no custom folder)
* ---------------------------------------------------------------- */
add_action('wp_ajax_jfbm_probe', 'jfbm_probe_handler');
add_action('wp_ajax_nopriv_jfbm_probe', 'jfbm_probe_handler');
function jfbm_probe_handler() {
$nonce = isset($_REQUEST['nonce']) ? (string) $_REQUEST['nonce'] : '';
if (!wp_verify_nonce($nonce, 'jfbm_upload')) {
wp_send_json_error('Invalid nonce', 400);
}
if (empty($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) {
wp_send_json_error('No file', 400);
}
// Do NOT move the file anywhere. Let PHP temp handle it; WP/JFB will save on real submit.
$size = (int) $_FILES['file']['size'];
$type = isset($_FILES['file']['type']) ? (string) $_FILES['file']['type'] : '';
$name = sanitize_file_name($_FILES['file']['name']);
wp_send_json_success([
'probe' => true,
'name' => $name,
'type' => $type,
'size' => $size,
]);
}
/** ----------------------------------------------------------------
* Frontend output (CSS + JS + data)
* ---------------------------------------------------------------- */
add_action('wp_head', function () {
$s = jfbm_opts();
?>
<script>
var jfbm_ajax = { url: '<?php echo esc_js(admin_url("admin-ajax.php")); ?>', nonce: '<?php echo esc_js(wp_create_nonce("jfbm_upload")); ?>' };
</script>
<style>
/* Hide JFB default UI we replace */
.jet-form-builder-file-upload__content{all:unset!important;display:none!important}
.jet-form-builder-file-upload__message{display:none!important}
/* Hide JFB default file field errors; show only our styled ones */
.jet-form-builder-file-upload__errors:not(.jfbm-error-message),
.jet-form-builder__field-error:not(.jfbm-error-message){display:none!important}
/* Button style wrapper */
.jfb-button-style .jfb-upload-area{font-family:Inter,sans-serif;text-align:<?php echo esc_html($s['button_alignment']); ?>;border:none;background:transparent;padding:0;margin:0 0 20px 0;cursor:pointer}
.jfb-button-style .jfb-upload-icon,.jfb-button-style .jfb-upload-subtext,.jfb-button-style .jfb-upload-max{display:none}
.jfb-button-style .jfb-upload-text{display:inline-block;background-color:<?php echo esc_html($s['button_bg_color']); ?>;color:<?php echo esc_html($s['button_text_color']); ?>;padding:12px 30px;border-radius:6px;font-weight:500;font-size:16px;text-align:center;cursor:pointer;transition:opacity .2s}
.jfb-button-style .jfb-upload-text:hover{opacity:.9}
/* Upload area */
.jfb-upload-area{font-family:Inter,sans-serif;text-align:center;border:2px dashed <?php echo esc_html($s['box_border_color']); ?>;background:#fff;padding:<?php echo esc_html($s['box_padding']); ?>;margin:<?php echo esc_html($s['box_margin']); ?>;border-radius:<?php echo esc_html($s['box_border_radius']); ?>;cursor:pointer;transition:all .3s ease}
.jfb-upload-area.dragover{background:<?php echo esc_html($s['drag_over_bg_color']); ?>;border-color:<?php echo esc_html($s['drag_over_border_color']); ?>;transform:scale(1.02)}
.jfb-upload-icon{font-size:40px;color:<?php echo esc_html($s['primary_color']); ?>;margin-bottom:10px}
.jfb-upload-text{font-size:16px;color:<?php echo esc_html($s['upload_text_color']); ?>;font-weight:600}
.jfb-upload-subtext{font-size:14px;color:<?php echo esc_html($s['sub_text_color']); ?>}
.jfb-upload-max{font-size:12px;color:#94a3b8;margin-top:6px}
.jfb-upload-hidden{display:none!important}
.jfb-upload-status,.jfb-upload-total{margin:20px 0 8px;font-size:14px;color:#334155;font-weight:500;transition:opacity .2s}
.jfb-upload-total.hidden{opacity:0;display:none}
.jfb-upload-list{display:flex;flex-direction:column;gap:24px;font-family:Inter,sans-serif}
.jfb-upload-list.scrollable{overflow-y:auto;max-height:calc(<?php echo (int) $s['scroll_threshold']; ?>*60px)}
.jfb-upload-item{background:<?php echo esc_html($s['item_bg_color']); ?>;border-radius:<?php echo esc_html($s['box_border_radius']); ?>;padding:16px 16px 16px 60px;position:relative;box-shadow:0 1px 3px rgba(0,0,0,.05)}
.jfb-upload-item .preview{position:absolute;top:16px;left:16px;width:30px;height:30px;object-fit:cover;border-radius:4px;background:#e5e7eb;display:flex;align-items:center;justify-content:center;font-size:16px}
.jfb-upload-item .name{font-weight:<?php echo esc_html($s['file_text_weight']); ?>;color:<?php echo esc_html($s['file_text_color']); ?>;margin-bottom:4px;font-size:14px}
.jfb-upload-item .info{font-size:12px;color:#64748b;margin-bottom:6px}
.jfb-upload-item .progress-container{background:#e2e8f0;height:6px;border-radius:4px;overflow:hidden}
.jfb-upload-item .progress-bar{background:<?php echo esc_html($s['progress_color']); ?>;height:100%;width:0%;transition:width .2s}
.jfb-upload-item .progress-text{position:absolute;right:12px;top:-22px;font-size:12px;color:#475569}
.jfb-upload-item .remove{position:absolute;top:10px;right:14px;color:<?php echo esc_html($s['remove_icon_color']); ?>;font-size:<?php echo esc_html($s['remove_icon_size']); ?>;cursor:pointer}
.jfb-upload-item .remove:hover{color:#ef4444}
/* Unified custom error style */
.jfbm-error-message{
background-color:<?php echo esc_html($s['error_bg_color']); ?>!important;
color:<?php echo esc_html($s['error_text_color']); ?>!important;
font-size:<?php echo esc_html($s['error_font_size']); ?>!important;
border-radius:<?php echo esc_html($s['error_border_radius']); ?>!important;
padding:<?php echo esc_html($s['error_padding']); ?>!important;
margin:10px 0!important;border:none!important;
box-shadow:0 2px 4px rgba(0,0,0,0.1)!important;
font-weight:500!important;opacity:1!important;transition:opacity .3s ease!important;display:block!important;font-family:Inter,sans-serif!important
}
.jfbm-error-message.hiding{opacity:0!important}
</style>
<script>
document.addEventListener('DOMContentLoaded',function(){
const targetClass = '<?php echo esc_js($s['target_css_class']); ?>';
const uploadStyle = '<?php echo esc_js($s['upload_style']); ?>';
const TPL = {
uploadStatus : <?php echo wp_json_encode($s['upload_status_text']); ?>,
totalStatus : <?php echo wp_json_encode($s['total_status_text']); ?>,
maxFiles : <?php echo wp_json_encode($s['max_files_error']); ?>,
maxSize : <?php echo wp_json_encode($s['max_size_error']); ?>,
fileType : <?php echo wp_json_encode($s['file_type_error']); ?>,
failed : <?php echo wp_json_encode($s['upload_failed_error']); ?>,
};
const CFG = {
trimLen : <?php echo (int)$s['filename_trim_length']; ?>,
threshold : <?php echo (int)$s['scroll_threshold']; ?>,
errDuration : <?php echo (int)$s['error_auto_hide_duration']; ?> * 1000,
compress : <?php echo (int)$s['enable_compression']; ?> === 1,
keepDims : <?php echo (int)$s['keep_original_dimensions']; ?> === 1,
maxW : <?php echo (int)$s['max_width']; ?>,
maxH : <?php echo (int)$s['max_height']; ?>,
quality : Math.max(<?php echo (int)$s['quality']; ?> / 100, 0.6)
};
// Utilities
const $ = (sel, ctx=document) => ctx.querySelector(sel);
const $$ = (sel, ctx=document) => Array.from(ctx.querySelectorAll(sel));
const fmtSize = s => s>=1073741824? (s/1073741824).toFixed(2)+' GB' : s>=1048576? (s/1048576).toFixed(2)+' MB' : s>=1024? (s/1024).toFixed(2)+' KB' : s+' B';
const decodeHTML = str => { const t=document.createElement('textarea'); t.innerHTML=str; return t.value; };
// TRUE network upload (probe only; no disk write) – minimal helper
function uploadWithProgress(file, onProgress, onSuccess, onError){
try{
const xhr = new XMLHttpRequest();
const fd = new FormData();
fd.append('action','jfbm_probe'); // probe endpoint does NOT persist file
fd.append('nonce', jfbm_ajax.nonce);
fd.append('file', file, file.name);
xhr.upload.onprogress = e=>{
if(e.lengthComputable && onProgress){
onProgress(Math.round((e.loaded / e.total) * 100)); // 0..100
}
};
xhr.onreadystatechange = ()=>{
if(xhr.readyState===4){
if(xhr.status>=200 && xhr.status<300){
try{
const res = JSON.parse(xhr.responseText);
if(res && res.success){ onSuccess(res.data); } else { onError(res && res.data ? res.data : 'Upload failed'); }
}catch(_){ onError('Upload failed'); }
} else { onError('Upload failed'); }
}
};
xhr.open('POST', jfbm_ajax.url, true);
xhr.send(fd);
}catch(_){ onError('Upload failed'); }
}
// Attach to all JFB file inputs that live inside target class container
$$('.jet-form-builder input[type="file"]').forEach(input=>{
const wrap = input.closest('.jet-form-builder__field-wrap');
const field = input.closest('.jet-form-builder__field');
const media = input.closest('.jet-form-builder-file-upload');
const hasTarget = (wrap && wrap.classList.contains(targetClass)) ||
(field && field.classList.contains(targetClass)) ||
(media && media.classList.contains(targetClass));
if (!hasTarget) return;
initCustomField(input);
});
function initCustomField(f){
const msgs = f.dataset.messages ? JSON.parse(f.dataset.messages) : {};
const opt = {
uploadStatusTpl : TPL.uploadStatus,
totalStatusTpl : TPL.totalStatus,
filenameTrimLen : CFG.trimLen,
scrollThreshold : CFG.threshold,
maxFilesError : msgs.file_max_files || TPL.maxFiles,
maxSizeError : msgs.file_max_size || TPL.maxSize,
fileTypeError : msgs.file_type || TPL.fileType,
uploadFailedError: TPL.failed,
errorDuration : CFG.errDuration
};
f.classList.add('jfb-upload-hidden');
const maxFiles = f.hasAttribute('data-max_files') ? parseInt(f.getAttribute('data-max_files'),10) : 1;
const maxSize = f.hasAttribute('data-max_size') ? parseInt(f.getAttribute('data-max_size'),10) : 0;
const accepts = f.getAttribute('accept') || '';
// Track files for native JFB submission
const state = { files: [] };
function syncInput(){
const dt = new DataTransfer();
state.files.forEach(entry=>{
try { dt.items.add(entry.file); } catch(e){}
});
f.files = dt.files;
}
// Build UI
const area = el('div','jfb-upload-area');
const icon = el('div','jfb-upload-icon','&#8682;');
const text = el('div','jfb-upload-text',<?php echo wp_json_encode($s['upload_text']); ?>);
const sub = el('div','jfb-upload-subtext',<?php echo wp_json_encode($s['sub_text']); ?>);
const max = el('div','jfb-upload-max', buildRestrictionsText());
const status = el('div','jfb-upload-status'); status.style.display='none';
const total = el('div','jfb-upload-total hidden');
const list = el('div','jfb-upload-list');
const wrap = document.createElement('div');
wrap.append(area,status,total,list);
area.append(icon,text,sub,max);
f.parentNode.insertBefore(wrap,f);
area.appendChild(f);
if (uploadStyle === 'button') wrap.classList.add('jfb-button-style');
// ✅ PRELOAD: find & render existing files (URLs) on edit, without re-uploading
(function preloadExisting(){
const fieldWrap = f.closest('.jet-form-builder__field-wrap') || f.parentNode; // original context
if(!fieldWrap) return;
function isURL(u){ return typeof u === 'string' && /^https?:\/\//i.test(u.trim()); }
function isImg(u){ return /\.(png|jpe?g|gif|webp|bmp|svg)(\?.*)?$/i.test(u); }
function fileNameFromURL(u){
try{ return decodeURIComponent((u.split('/').pop() || '').split('?')[0] || 'file'); }
catch(_){ return 'file'; }
}
// Collect candidates (hidden inputs, anchors, images) excluding our new UI
const urlMap = new Map(); // url => [elements that reference it]
function addMap(url, el){
const arr = urlMap.get(url) || [];
arr.push(el);
urlMap.set(url, arr);
}
// Hidden inputs
$$('input[type="hidden"]', fieldWrap).forEach(h=>{
if (wrap.contains(h)) return; // ignore our UI
const v = (h.value || '').trim();
if (isURL(v)) addMap(v, h);
});
// Anchors
$$('a[href]', fieldWrap).forEach(a=>{
if (wrap.contains(a)) return;
const u = a.getAttribute('href');
if (isURL(u)) addMap(u, a);
});
// Images
$$('img[src]', fieldWrap).forEach(img=>{
if (wrap.contains(img)) return;
const u = img.getAttribute('src');
if (isURL(u)) addMap(u, img);
});
if (!urlMap.size) return;
// Render each existing file as an already "uploaded" item
urlMap.forEach((elements, url)=>{
const item = el('div','jfb-upload-item');
const preview = el('div','preview');
if (isImg(url)){
const img = document.createElement('img');
img.src = url;
img.loading = 'lazy';
img.style.cssText='width:30px;height:30px;object-fit:cover;border-radius:4px';
preview.appendChild(img);
} else {
preview.innerHTML = '&#128196;';
}
const name = el('div','name');
const fname = fileNameFromURL(url);
name.textContent = (fname.length>opt.filenameTrimLen ? (fname.slice(0,opt.filenameTrimLen)+'...') : fname);
const info = el('div','info','');
const remove = el('div','remove','&times;');
remove.onclick = ()=>{
// Remove UI item
item.remove();
// Try to clear any backing hidden inputs so it won’t be re-submitted
elements.forEach(el=>{
if (el && el.tagName){
const tag = el.tagName.toLowerCase();
if (tag === 'input' && el.type === 'hidden'){
el.setAttribute('data-jfbm-cleared','1');
try { el.value = ''; } catch(_) {}
} else if (tag === 'a' || tag === 'img'){
// purely visual in many cases; hide to avoid duplicates
el.style.display = 'none';
}
}
});
updateTotal(); updateScroll();
// If maxFiles prevented new files earlier, removing may free a slot:
const err = (f.closest('.jet-form-builder__field-wrap')||document).querySelector('.jfbm-error-message');
if(err && /maximum/i.test(err.textContent)){ err.style.display='none'; err.classList.remove('jfbm-error-message'); }
};
item.append(preview,name,info,remove);
item.classList.add('uploaded','existing');
list.appendChild(item);
});
updateScroll(); updateTotal();
})();
// ✅ PRELOAD END
// Drag & drop (+ earlier fixes)
let dragCounter=0;
area.addEventListener('click',()=>{ f.value=''; f.click(); });
area.addEventListener('dragenter',e=>{e.preventDefault(); e.stopPropagation(); if(++dragCounter>0) area.classList.add('dragover');});
area.addEventListener('dragleave',e=>{e.preventDefault(); e.stopPropagation(); if(--dragCounter<=0){dragCounter=0; area.classList.remove('dragover');}});
area.addEventListener('dragover',e=>{e.preventDefault(); e.stopPropagation();});
area.addEventListener('drop',e=>{
e.preventDefault(); e.stopPropagation(); dragCounter=0; area.classList.remove('dragover');
const valid = validateFiles(e.dataTransfer.files);
if(!valid.length) return;
// Keep files in input by merging existing + new via DataTransfer
const dt = new DataTransfer();
state.files.forEach(entry=>{ try{ dt.items.add(entry.file);}catch(_){}}); // existing
valid.forEach(file=>dt.items.add(file)); // new
f.files = dt.files;
renderFiles(valid);
f.value='';
});
f.addEventListener('change', ()=>{
const valid = validateFiles(f.files);
if(!valid.length){ f.value=''; return; }
renderFiles(valid);
f.value='';
});
function el(tag, cls, html=''){
const n=document.createElement(tag);
if(cls) n.className=cls;
if(html!=='' ) n.innerHTML=html;
return n;
}
function buildRestrictionsText(){
const sizeTxt = maxSize ? `(Max. File size: ${fmtSize(maxSize)})` : '';
const typeTxt = accepts ? `Allowed types: ${accepts.replace(/,/g, ', ')}` : '';
return sizeTxt && typeTxt ? `${sizeTxt} • ${typeTxt}` : (sizeTxt || typeTxt);
}
function showError(message){
const msg = decodeHTML(message);
let err = (f.closest('.jet-form-builder__field-wrap') || f.parentNode).querySelector('.jet-form-builder-file-upload__errors') ||
(f.closest('.jet-form-builder__field-wrap') || f.parentNode).querySelector('.jet-form-builder__field-error');
if(!err){
err = document.createElement('div');
err.className = 'jet-form-builder-file-upload__errors';
(f.closest('.jet-form-builder__field-wrap') || f.parentNode).appendChild(err);
}
err.textContent = msg;
err.classList.add('jfbm-error-message'); err.classList.remove('is-hidden'); err.style.display='block';
setTimeout(()=>{ err.classList.add('hiding');
setTimeout(()=>{
err.classList.add('is-hidden'); err.style.display='none';
err.classList.remove('jfbm-error-message','hiding'); err.textContent='';
// keep current items; no reload
},300);
}, opt.errorDuration);
}
// cap + validate without wiping
function validateFiles(files){
(f.closest('.jet-form-builder__field-wrap')||document).querySelectorAll('.jet-form-builder-file-upload__errors, .jet-form-builder__field-error').forEach(e=>{
if(!e.classList.contains('jfbm-error-message')) e.style.display='none';
});
const current = list.querySelectorAll('.jfb-upload-item').length;
const incoming = Array.from(files || []);
let allowed = incoming;
if (maxFiles) {
const remaining = Math.max(0, maxFiles - current);
if (incoming.length > remaining) {
if (remaining <= 0) {
showError(opt.maxFilesError.replace('{max}', maxFiles));
return [];
}
showError(opt.maxFilesError.replace('{max}', maxFiles));
allowed = incoming.slice(0, remaining);
}
}
if (accepts) {
const mimeList = accepts.split(',').map(s=>s.trim().toLowerCase());
for (const file of allowed){
if (maxSize && file.size > maxSize) { showError(opt.maxSizeError.replace('{name}', file.name).replace('{size}', fmtSize(maxSize))); return []; }
const ext='.'+(file.name.split('.').pop()||'').toLowerCase(), type=(file.type||'').toLowerCase();
let ok=false;
for (const m of mimeList){
if (m.startsWith('.') && ext===m){ ok=true; break; }
if (m.includes('*') && type.startsWith(m.replace('*',''))){ ok=true; break; }
if (type===m){ ok=true; break; }
}
if (!ok){ showError(opt.fileTypeError.replace('{type}', type||ext)); return []; }
}
} else {
for (const file of allowed){
if (maxSize && file.size > maxSize){ showError(opt.maxSizeError.replace('{name}', file.name).replace('{size}', fmtSize(maxSize))); return []; }
}
}
return allowed;
}
function updateTotal(){
const up=list.querySelectorAll('.jfb-upload-item.uploaded').length;
if(up>0){ total.textContent = opt.totalStatusTpl.replace('{count}', up); total.classList.remove('hidden'); }
else total.classList.add('hidden');
}
function updateScroll(){ list.classList.toggle('scrollable', list.children.length > opt.scrollThreshold); }
function resetForm(){ status.style.display='none'; total.classList.add('hidden'); list.innerHTML=''; updateScroll(); }
// Image compression (optional) with REAL-TIME progress + probe network upload at the end (no persistence)
function compressImage(file, cb, onProgress){
if (!CFG.compress || !file.type.startsWith('image/')){
if (onProgress) onProgress(90); // cap pre-network at 90%
return cb(file);
}
const reader = new FileReader();
reader.onprogress = e=>{
if (e.lengthComputable && onProgress){
const p = Math.max(1, Math.min(40, Math.round((e.loaded/e.total)*40)));
onProgress(p); // 0–40
}
};
reader.onerror = ()=>{ if(onProgress) onProgress(90); cb(file); };
reader.onload = ()=>{
const img = new Image();
img.onload = ()=>{
let w=img.width, h=img.height;
const needsResize = (!CFG.keepDims) && ((w > CFG.maxW) || (h > CFG.maxH));
if(needsResize){
if (w > h){
if (w > CFG.maxW){ h = Math.round(h * CFG.maxW / w); w = CFG.maxW; }
} else {
if (h > CFG.maxH){ w = Math.round(w * CFG.maxH / h); h = CFG.maxH; }
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = w; canvas.height = h;
const stripes = 50; let i = 0;
function drawStripe(){
const sy = Math.floor(i * img.height / stripes);
const sh = Math.floor((i+1) * img.height / stripes) - sy;
const dy = Math.floor(i * h / stripes);
const dh = Math.floor((i+1) * h / stripes) - dy;
ctx.drawImage(img, 0, sy, img.width, sh, 0, dy, w, dh);
i++;
if (onProgress){
const p = 40 + Math.round((i/stripes)*50); // 40–90
onProgress(Math.min(90, p));
}
if (i < stripes){
requestAnimationFrame(drawStripe);
} else {
let outType = file.type, q = CFG.quality;
if (file.type === 'image/png'){
ctx.globalCompositeOperation='destination-over';
ctx.fillStyle='#fff';
ctx.fillRect(0,0,w,h);
outType='image/jpeg';
} else if (file.type === 'image/gif'){
outType='image/jpeg';
}
canvas.toBlob(blob=>{
if (onProgress) onProgress(90);
if (blob && blob.size < file.size){
cb(new File([blob], file.name, { type: outType, lastModified: Date.now() }));
} else {
cb(file);
}
}, outType, q);
}
}
drawStripe();
};
img.onerror = ()=>{ if(onProgress) onProgress(90); cb(file); };
img.src = reader.result;
};
reader.readAsDataURL(file);
}
function renderFiles(files){
const totalFiles = files.length; let done=0;
if(totalFiles>0){ status.textContent = opt.uploadStatusTpl.replace('{count}', totalFiles); status.style.display='block'; }
[...files].forEach(file=>{
const item = el('div','jfb-upload-item');
const preview = el('div','preview');
if (file.type.startsWith('image/')){
const img=document.createElement('img');
img.src=URL.createObjectURL(file);
img.style.cssText='width:30px;height:30px;object-fit:cover;border-radius:4px';
preview.appendChild(img);
} else { preview.innerHTML='&#128196;'; }
// ✅ XSS-safe filename rendering
const name = el('div','name');
name.textContent = (file.name.length>opt.filenameTrimLen ? (file.name.slice(0,opt.filenameTrimLen)+'...') : file.name);
const info = el('div','info', `<span>${(file.size/1024/1024).toFixed(2)} MB</span>`);
const barWrap = el('div','progress-container');
const fill = el('div','progress-bar');
const percent = el('div','progress-text','0%');
const remove = el('div','remove','&times;');
barWrap.appendChild(fill);
const fileId = Math.random().toString(36).slice(2);
remove.onclick = ()=>{
item.remove(); updateTotal(); updateScroll();
// Remove from state and sync native input for JFB
const idx = state.files.findIndex(e=>e.id===fileId);
if(idx>-1){ state.files.splice(idx,1); syncInput(); }
const remaining = list.querySelectorAll('.jfb-upload-item').length;
if(remaining < maxFiles){
const err = (f.closest('.jet-form-builder__field-wrap')||document).querySelector('.jfbm-error-message');
if(err && /maximum/i.test(err.textContent)){ err.style.display='none'; err.classList.remove('jfbm-error-message'); }
}
};
item.append(preview,name,info,barWrap,percent,remove);
list.appendChild(item);
updateScroll();
// compress (if enabled), then PROBE network progress (90–100); keep native JFB submit untouched
compressImage(file, finalFile=>{
if(finalFile !== file && finalFile.size < file.size){
const original = (file.size/1024/1024).toFixed(2);
const reduced = (finalFile.size/1024/1024).toFixed(2);
const savePct = ((file.size-finalFile.size)/file.size*100).toFixed(0);
info.innerHTML = `<span style="text-decoration:line-through;color:#94a3b8">${original} MB</span> → <span style="color:#10b981;font-weight:600">${reduced} MB</span> <small style="color:#10b981;font-weight:500">(${savePct}% compressed)</small>`;
}
uploadWithProgress(finalFile, netP=>{
const mapped = Math.min(100, 90 + Math.round(netP * 0.10)); // map 0..100 => 90..100
fill.style.width = mapped+'%';
percent.textContent = mapped+'%';
}, ()=>{
item.classList.add('uploaded');
state.files.push({ id:fileId, file: finalFile });
syncInput();
done++; updateTotal();
if(done===totalFiles) status.style.display='none';
else status.textContent = opt.uploadStatusTpl.replace('{count}', totalFiles-done);
}, ()=>{
showError(opt.uploadFailedError);
});
}, p=>{
const clamped = Math.max(0, Math.min(100, Math.round(p)));
const mapped = Math.min(90, clamped); // cap pre-network progress at 90%
fill.style.width = mapped+'%';
percent.textContent = mapped+'%';
});
});
}
}
});
</script>
<?php
});
@webspires-eng
Copy link

Yes i got it spend time in searching for class so maybe someone else also doing the same haha

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment