Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/paid-memberships-pro/js/pmpro-dashboard.js

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + /**
2 + * PMPro Dashboard - Updated for 3-column grid
3 + * This code allows users to reorder dashboard widgets via drag and drop.
4 + * It also disables the default WordPress postbox functionality to prevent conflicts.
5 + * This script uses jQuery UI's sortable functionality to manage the drag-and-drop behavior.
6 + * It stores the initial positions of the widgets and animates them to their new positions when reordered.
7 + * The new order is saved via AJAX when the user stops dragging a widget.
8 + */
9 +
10 + jQuery(document).ready(function () {
11 +
12 + // ARIA live region helper for announcements
13 + function ensureLiveRegion() {
14 + if (!jQuery('#pmpro-dashboard-live-region').length) {
15 + jQuery('body').append('<div id="pmpro-dashboard-live-region" role="status" aria-live="polite" aria-atomic="true" style="position:absolute;left:-9999px;height:1px;width:1px;overflow:hidden;"></div>');
16 + }
17 + }
18 +
19 + // Function to announce drag actions
20 + function announceDrag($item, action) {
21 + ensureLiveRegion();
22 + const label = $item.find('.hndle').text().trim() || $item.attr('id');
23 + const text = label + ' ' + action;
24 + jQuery('#pmpro-dashboard-live-region').text(text);
25 + }
26 +
27 + let pmproDashboardPositions = {};
28 + let isAnimating = false;
29 +
30 + const dashboardForm = jQuery('#dashboard-widgets', '#pmpro-dashboard-form');
31 +
32 + // Store initial positions
33 + function storePositions() {
34 + pmproDashboardPositions = {};
35 + dashboardForm.children('.postbox').each(function() {
36 + if (this.id) {
37 + pmproDashboardPositions[this.id] = this.getBoundingClientRect();
38 + }
39 + });
40 + }
41 +
42 + // Animate elements to their new positions
43 + function animateToNewPositions(excludeElement) {
44 + if (isAnimating) return;
45 + isAnimating = true;
46 +
47 + dashboardForm.children('.postbox').not(excludeElement).each(function() {
48 + const oldRect = pmproDashboardPositions[this.id];
49 + const newRect = this.getBoundingClientRect();
50 +
51 + if (!oldRect) return;
52 +
53 + const dx = oldRect.left - newRect.left;
54 + const dy = oldRect.top - newRect.top;
55 +
56 + if (Math.abs(dx) > 1 || Math.abs(dy) > 1) {
57 + // Apply transform instantly
58 + this.style.transition = 'none';
59 + this.style.transform = `translate(${dx}px, ${dy}px)`;
60 +
61 + // Force reflow
62 + this.offsetHeight;
63 +
64 + // Animate back to natural position
65 + this.style.transition = 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
66 + this.style.transform = 'translate(0, 0)';
67 + }
68 + });
69 +
70 + // Clean up after animation
71 + setTimeout(() => {
72 + dashboardForm.children('.postbox').each(function() {
73 + this.style.transition = '';
74 + this.style.transform = '';
75 + });
76 + isAnimating = false;
77 + }, 300);
78 + }
79 +
80 + dashboardForm.sortable({
81 + items: '.postbox[role="listitem"]',
82 + handle: '.hndle',
83 + cursor: 'move',
84 + opacity: 0.8,
85 + placeholder: 'ui-sortable-placeholder',
86 + tolerance: 'pointer',
87 + distance: 10, // Reduce distance for easier drag initiation
88 + delay: 8, // Reduce delay for more responsive feel
89 + containment: 'parent', // Less restrictive containment
90 + start: function(event, ui) {
91 + // Store positions before drag starts
92 + storePositions();
93 + ui.item.addClass('pmpro-dragging');
94 + ui.item.attr('aria-grabbed', 'true');
95 + announceDrag(ui.item, 'picked up');
96 +
97 + // Copy grid column span from dragged item to placeholder
98 + const columnSpan = ui.item.css('grid-column');
99 + const spanClass = ui.item.attr('class').match(/pmpro-colspan-(\d+)/);
100 +
101 + if (spanClass) {
102 + // Ensure we don't exceed 3 columns in the new grid
103 + const spanValue = Math.min(parseInt(spanClass[1]));
104 + ui.placeholder.addClass('pmpro-colspan-' + spanValue);
105 + } else if (columnSpan && columnSpan !== 'auto') {
106 + ui.placeholder.css('grid-column', columnSpan);
107 + }
108 +
109 + // Set placeholder height to match dragged item
110 + const itemHeight = ui.item.height();
111 + ui.placeholder.css({
112 + 'height': itemHeight + 'px',
113 + 'box-sizing': 'border-box'
114 + });
115 + },
116 +
117 + change: function(event, ui) {
118 + // Only animate when placeholder position actually changes
119 + if (!isAnimating) {
120 + requestAnimationFrame(() => {
121 + animateToNewPositions(ui.item);
122 + // Update positions after animation starts
123 + setTimeout(() => {
124 + storePositions();
125 + }, 10);
126 + });
127 + }
128 + },
129 +
130 + over: function(event, ui) {
131 + // Force placeholder visibility when over the sortable area
132 + ui.placeholder.show();
133 + },
134 +
135 + out: function(event, ui) {
136 + // Keep placeholder visible even when temporarily outside
137 + ui.placeholder.show();
138 + },
139 +
140 + beforeStop: function(event, ui) {
141 + // Clean up any ongoing animations
142 + dashboardForm.children('.postbox').each(function() {
143 + this.style.transition = '';
144 + this.style.transform = '';
145 + });
146 + },
147 +
148 + stop: function(event, ui) {
149 + ui.item.removeClass('pmpro-dragging');
150 + ui.item.attr('aria-grabbed', 'false');
151 + announceDrag(ui.item, 'dropped');
152 +
153 + // Clean up placeholder classes and styles - Updated for 3-column grid
154 + ui.placeholder.removeClass(function (index, className) {
155 + return (className.match(/(^|\s)pmpro-colspan-\S+/g) || []).join(' ');
156 + });
157 + ui.placeholder.css({
158 + 'grid-column': '',
159 + 'height': ''
160 + });
161 +
162 + isAnimating = false;
163 + },
164 +
165 + update: function(event, ui) {
166 + // Add drop animation
167 + ui.item.addClass('pmpro-just-dropped');
168 + setTimeout(function() {
169 + ui.item.removeClass('pmpro-just-dropped');
170 + }, 250);
171 +
172 + // Get all metabox IDs in their new order
173 + var newOrder = [];
174 + jQuery('.postbox', dashboardForm).each(function() {
175 + var id = jQuery(this).attr('id');
176 + if (id) {
177 + newOrder.push(id);
178 + }
179 + });
180 +
181 + // Save the new order via AJAX
182 + if (newOrder.length > 0) {
183 + var nonceValue = jQuery('#pmpro_metabox_nonce').val();
184 +
185 + if (!nonceValue) {
186 + console.error('Nonce field not found or empty');
187 + return;
188 + }
189 +
190 + // Save the new sort order
191 + savePosition(newOrder, nonceValue);
192 + }
193 + }
194 + });
195 +
196 + /**
197 + * Keyboard Accessibility for Dragging
198 + * Allows users to pick up, move, and drop items using keyboard keys.
199 + * Uses space/enter to pick up and drop, arrow keys to move.
200 + */
201 + let $dragged = null;
202 +
203 + jQuery('#dashboard-widgets').on('keydown', '.hndle', function(e) {
204 + const $item = jQuery(this).closest('.postbox[role="listitem"]');
205 +
206 + if (($dragged === null) && (e.key === ' ' || e.key === 'Enter')) {
207 + // Pick up the item
208 + e.preventDefault();
209 + $dragged = $item;
210 + $item.attr('aria-grabbed', 'true').addClass('pmpro-dragged-by-keyboard');
211 + announceDrag($item, 'Picked up (use arrows to move, enter/space to drop)');
212 + } else if ($dragged && $item[0] === $dragged[0]) {
213 + // While holding an item, allow up/down/left/right to move
214 + if (['ArrowUp', 'ArrowLeft'].includes(e.key)) {
215 + e.preventDefault();
216 + let $prev = $item.prevAll('.postbox[role="listitem"]').first();
217 + if ($prev.length) {
218 + $prev.before($item);
219 + announceDrag($item, 'moved');
220 + $item.find('.hndle').focus();
221 + }
222 + } else if (['ArrowDown', 'ArrowRight'].includes(e.key)) {
223 + e.preventDefault();
224 + let $next = $item.nextAll('.postbox[role="listitem"]').first();
225 + if ($next.length) {
226 + $next.after($item);
227 + announceDrag($item, 'moved');
228 + $item.find('.hndle').focus();
229 + }
230 + } else if (e.key === ' ' || e.key === 'Enter') {
231 + // Drop
232 + e.preventDefault();
233 + $item.attr('aria-grabbed', 'false').removeClass('pmpro-dragged-by-keyboard');
234 + announceDrag($item, 'dropped');
235 + $dragged = null;
236 + // Optionally: trigger your save order logic here (AJAX)
237 + var newOrder = [];
238 + jQuery('.postbox[role="listitem"]', dashboardForm).each(function() {
239 + var id = jQuery(this).attr('id');
240 + if (id) {
241 + newOrder.push(id);
242 + }
243 + });
244 + if (newOrder.length > 0) {
245 + var nonceValue = jQuery('#pmpro_metabox_nonce').val();
246 + if (nonceValue) {
247 + // Save the new sort order
248 + savePosition(newOrder, nonceValue);
249 + }
250 + }
251 + } else if (e.key === 'Escape') {
252 + // Cancel
253 + e.preventDefault();
254 + $item.attr('aria-grabbed', 'false').removeClass('pmpro-dragged-by-keyboard');
255 + announceDrag($item, 'cancelled');
256 + $dragged = null;
257 + }
258 + }
259 + });
260 +
261 + // Visual focus for grabbed item
262 + jQuery(document).on('focusin focusout', '.hndle', function(event) {
263 + jQuery(this).closest('.postbox[role="listitem"]').toggleClass('pmpro-keyboard-focus', event.type === 'focusin');
264 + });
265 +
266 + // Disable WordPress postbox functionality here
267 + // This prevents conflicts with our custom drag-and-drop functionality.
268 + if (typeof postboxes !== 'undefined') {
269 + postboxes.handle_click = function() { return false; };
270 + postboxes.add_postbox_toggles = function() { return false; };
271 + }
272 +
273 + // Helper function to save the new order via AJAX
274 + function savePosition( newOrder, nonceValue ) {
275 + if (typeof ajaxurl === 'undefined') {
276 + console.error('AJAX URL is not defined. Ensure that the script is enqueued properly.');
277 + return;
278 + }
279 + // Ensure nonce value is valid
280 + if (!nonceValue || nonceValue === '') {
281 + console.error('Nonce value is not defined or empty. Ensure the nonce field exists in the form.');
282 + return;
283 + }
284 + // Make the AJAX request to save the new order
285 + jQuery.ajax({
286 + url: ajaxurl,
287 + type: 'POST',
288 + data: {
289 + action: 'pmpro_save_metabox_order',
290 + pmpro_metabox_nonce: nonceValue,
291 + order: newOrder.join(',')
292 + },
293 + dataType: 'json',
294 + timeout: 5000,
295 + error: function(xhr, status, error) {
296 + console.error('AJAX Error - Status:', status);
297 + console.error('AJAX Error - Error:', error);
298 + }
299 + });
300 + }
301 + });