Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/paid-memberships-pro/js/pmpro-dashboard.js
Keine Baseline-Datei – Diff nur gegen leer.
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
+
});