Skip to content

Commit 2c44581

Browse files
committed
gpnf-sortable-entries.php: Added experimental snippet to allow sorting GPNF entries.
1 parent 02b8d7d commit 2c44581

File tree

1 file changed

+380
-0
lines changed

1 file changed

+380
-0
lines changed
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
<?php
2+
/**
3+
* Gravity Perks // Nested Forms // Sortable Entries
4+
* https://gravitywiz.com/documentation/gravity-forms-nested-forms/
5+
*
6+
* Instructions:
7+
*
8+
* 1. Install this code as a plugin or as a snippet (https://gravitywiz.com/documentation/how-do-i-install-a-snippet/)
9+
* 2. Customize the configuration as needed at the bottom of this file. By default, this snippet will enable sorting
10+
* for all Nested Form fields on all forms.
11+
*
12+
* Plugin Name: GP Nested Forms - Sortable Entries
13+
* Plugin URI: https://gravitywiz.com/documentation/gravity-forms-nested-forms/
14+
* Description: Enable sorting for Nested Form entries.
15+
* Author: Gravity Wiz
16+
* Version: 0.1
17+
* Author URI: https://gravitywiz.com/
18+
*/
19+
20+
class GPNF_Sortable_Entries {
21+
22+
/**
23+
* @var array{
24+
* form_id?: int,
25+
* field_id?: int|int[]
26+
* } $_args
27+
*/
28+
private $_args = array();
29+
30+
/**
31+
* @param array{
32+
* form_id?: int,
33+
* field_id?: int|int[]
34+
* } $args
35+
*/
36+
public function __construct( $args = array() ) {
37+
38+
// set our default arguments, parse against the provided arguments, and store for use throughout the class
39+
$this->_args = wp_parse_args( $args, array(
40+
'form_id' => false,
41+
'field_id' => false,
42+
) );
43+
44+
// do version check in the init to make sure if GF is going to be loaded, it is already loaded
45+
add_action( 'init', array( $this, 'init' ) );
46+
47+
}
48+
49+
/**
50+
* @return void
51+
*/
52+
public function init() {
53+
54+
// time for hooks
55+
add_filter( 'gform_pre_render', array( $this, 'load_form_script' ), 10, 2 );
56+
add_action( 'gform_register_init_scripts', array( $this, 'add_init_script' ), 10, 2 );
57+
add_filter( 'gpnf_should_use_static_value', array( $this, 'use_static_value' ), 10, 3 );
58+
add_action( 'wp_ajax_gpnf_update_entry_order', array( $this, 'update_entry_order' ) );
59+
add_action( 'wp_ajax_no_priv_gpnf_update_entry_order', array( $this, 'update_entry_order' ) );
60+
add_filter( 'gpnf_init_script_args', array( $this, 'add_init_script_args' ), 10, 3 );
61+
62+
}
63+
64+
/**
65+
* Filter the init script to sort the entries based on what's set in the cookie.
66+
*
67+
* @param array $args {
68+
*
69+
* @var int $formId The current form ID.
70+
* @var int $fieldId The field ID of the Nested Form field.
71+
* @var int $nestedFormId The form ID of the nested form.
72+
* @var string $modalTitle The title to be displayed in the modal header.
73+
* @var string $editModalTitle The title to be displayed in the modal header when editing an existing entry.
74+
* @var array $displayFields The fields which will be displayed in the Nested Forms entries view.
75+
* @var array $entries An array of modified entries, including only their display values.
76+
* @var string $ajaxUrl The URL to which AJAX requests will be posted.
77+
* @var int $modalWidth The default width of the modal; defaults to 700.
78+
* @var mixed $modalHeight The default height of the modal; defaults to 'auto' which will automatically size the modal based on its contents.
79+
* @var string $modalClass The class that will be attached to the modal for styling.
80+
* @var string $modalHeaderColor A HEX color that will be set as the default background color of the modal header.
81+
* @var bool $hasConditionalLogic Indicate whether the current form has conditional logic enabled.
82+
* @var bool $hasConditionalLogic Indicate whether the current form has conditional logic enabled.
83+
* @var bool $enableFocusTrap Whether the nested form should use a focus trap when open to prevent tabbing outside the nested form.
84+
*
85+
* }
86+
* @param GF_Field $field The current Nested Form field.
87+
* @param array $form The current form.
88+
*/
89+
public function add_init_script_args( $args, $field, $form ) {
90+
if ( ! $this->is_applicable_form( $form ) || ! $this->is_applicable_field( $field->id ) ) {
91+
return $args;
92+
}
93+
94+
$session = new GPNF_Session( $form['id'] );
95+
$cookie_name = $session->get_cookie_name();
96+
97+
$cookie_raw = rgar( $_COOKIE, $cookie_name );
98+
99+
if ( ! $cookie_raw ) {
100+
return $args;
101+
}
102+
103+
$cookie = json_decode( stripslashes( $_COOKIE[ $cookie_name ] ), true );
104+
$cookie_entries = rgars( $cookie, 'nested_entries/' . $field->id );
105+
106+
if ( empty( $cookie_entries ) ) {
107+
return $args;
108+
}
109+
110+
// Sort $args['entries'] which contains Gravity Forms entries arrays. They contain an 'id' key.
111+
// $cookie_entries contains the entry IDs in the order they should be displayed.
112+
$sorted_entries = array();
113+
114+
foreach ( $cookie_entries as $entry_id ) {
115+
foreach ( $args['entries'] as $entry ) {
116+
if ( $entry['id'] == $entry_id ) {
117+
$sorted_entries[] = $entry;
118+
break;
119+
}
120+
}
121+
}
122+
123+
$args['entries'] = $sorted_entries;
124+
125+
return $args;
126+
}
127+
128+
/**
129+
* Handles updating the entry order in the GPNF session after a sort event.
130+
*
131+
* @return void
132+
*/
133+
public function update_entry_order() {
134+
check_ajax_referer( 'gpnf_refresh_markup', 'nonce' );
135+
136+
$form_id = rgpost( 'formId' );
137+
$field_id = rgpost( 'fieldId' );
138+
$entry_ids = rgpost( 'entryIds' );
139+
140+
$session = new GPNF_Session( $form_id );
141+
$cookie_name = $session->get_cookie_name();
142+
143+
// Most of GPNF_Session is private so we need to modify the cookie directly.
144+
$cookie = json_decode( stripslashes( $_COOKIE[ $cookie_name ] ), true );
145+
146+
// If 'nested_entries' is not set for the field, just die.
147+
if ( ! isset( $cookie['nested_entries'][ $field_id ] ) ) {
148+
die();
149+
}
150+
151+
// Update the nested_entries array with the new order.
152+
$cookie['nested_entries'][ $field_id ] = $entry_ids;
153+
154+
// Update the cookie.
155+
setcookie( $cookie_name, json_encode( $cookie ), 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl() );
156+
157+
die();
158+
}
159+
160+
/**
161+
* Use static values if sorting is enabled, otherwise a GF_Query will be used to get the child entries and
162+
* the sorting will not be applied.
163+
*
164+
* @param bool $should_use_static_value Should the field's value be static?
165+
* @param \GP_Field_Nested_Form $field The current Nested Form field.
166+
* @param array $entry The current entry.
167+
*/
168+
public function use_static_value( $should_use_static_value, $field, $entry ) {
169+
if ( ! $this->is_applicable_form( $field->formId ) || ! $this->is_applicable_field( $field->id ) ) {
170+
return $should_use_static_value;
171+
}
172+
173+
return true;
174+
}
175+
176+
/**
177+
* @param array $form
178+
* @param bool $is_ajax_enabled
179+
*
180+
* @return array
181+
*/
182+
public function load_form_script( $form, $is_ajax_enabled ) {
183+
if ( $this->is_applicable_form( $form ) && ! has_action( 'wp_footer', array( $this, 'output_script' ) ) ) {
184+
add_action( 'wp_footer', array( $this, 'output_script' ) );
185+
add_action( 'gform_preview_footer', array( $this, 'output_script' ) );
186+
}
187+
188+
return $form;
189+
}
190+
191+
/**
192+
* @return void
193+
*/
194+
public function output_script() {
195+
?>
196+
197+
<script type="text/javascript">
198+
199+
( function( $ ) {
200+
201+
window.<?php echo __CLASS__; ?> = function( args ) {
202+
203+
var self = this;
204+
205+
// copy all args to current object: (list expected props)
206+
for( var prop in args ) {
207+
if( args.hasOwnProperty( prop ) ) {
208+
self[ prop ] = args[ prop ];
209+
}
210+
}
211+
212+
self.isApplicableField = function(fieldId) {
213+
if ( typeof fieldId !== 'number' ) {
214+
fieldId = parseInt( fieldId );
215+
}
216+
217+
if ( ! self.fieldId ) {
218+
return true;
219+
}
220+
221+
if ( typeof self.fieldId !== 'object' ) {
222+
self.fieldId = [ self.fieldId ];
223+
}
224+
225+
// Ensure fieldIds are all numbers
226+
self.fieldId = self.fieldId.map( function( fieldId ) {
227+
if ( typeof fieldId === 'string' ) {
228+
fieldId = parseInt( fieldId );
229+
}
230+
231+
return fieldId;
232+
} );
233+
234+
return self.fieldId.indexOf( fieldId ) !== -1;
235+
}
236+
237+
gform.addAction( 'gpnf_session_initialized', function(gpnf) {
238+
if ( gpnf.formId != self.formId ) {
239+
return;
240+
}
241+
242+
var $form = $( '#gform_' + self.formId );
243+
var $field = $form.find( '#field_' + self.formId + '_' + self.fieldId );
244+
var $entries = $field.find('.gpnf-nested-entries tbody');
245+
246+
$entries.sortable({
247+
axis: 'y',
248+
// Logic to help with keeping the width of <tr>'s correct when dragging them
249+
helper: function(e, tr) {
250+
var helperRow = tr
251+
.clone()
252+
.css('display', 'table');
253+
254+
return helperRow;
255+
},
256+
update: function(event, ui) {
257+
var sortedEntryIds = $entries.find('tr').map(function() {
258+
return $(this).data('entryid');
259+
}).get();
260+
261+
var currentEntries = gpnf.viewModel.entries();
262+
263+
// Sort the entries based on the sortedEntryIds
264+
var sortedEntries = sortedEntryIds.map(function(entryId) {
265+
return currentEntries.find(function(entry) {
266+
return entry.id == entryId;
267+
});
268+
});
269+
270+
gpnf.viewModel.entries( sortedEntries );
271+
272+
// Send AJAX request to update the entry order
273+
$.post( gpnf.ajaxUrl, {
274+
action: 'gpnf_update_entry_order',
275+
gpnf_context: gpnf.sessionData.gpnf_context,
276+
nonce: GPNFData.nonces.refreshMarkup, // Using an existing nonce.
277+
formId: gpnf.formId,
278+
fieldId: gpnf.fieldId,
279+
entryIds: sortedEntryIds
280+
} );
281+
}
282+
});
283+
284+
console.log(gpnf)
285+
286+
gpnf.viewModel.entries.subscribe( function( entries ) {
287+
$entries.sortable( 'refresh' );
288+
} );
289+
} );
290+
291+
}
292+
293+
} )( jQuery );
294+
295+
</script>
296+
297+
<?php
298+
}
299+
300+
/**
301+
* @param array $form
302+
*
303+
* @return void
304+
*/
305+
public function add_init_script( $form ) {
306+
307+
if ( ! $this->is_applicable_form( $form ) ) {
308+
return;
309+
}
310+
311+
$args = array(
312+
'formId' => $this->_args['form_id'],
313+
'fieldId' => $this->_args['field_id'],
314+
);
315+
316+
// Enqueue jQuery UI Sortable
317+
wp_enqueue_script( 'jquery-ui-sortable' );
318+
319+
$script = 'new ' . __CLASS__ . '( ' . json_encode( $args ) . ' );';
320+
$slug = implode( '_', array( strtolower( __CLASS__ ), $this->_args['form_id'], $this->_args['field_id'] ) );
321+
322+
GFFormDisplay::add_init_script( $form['id'], $slug, GFFormDisplay::ON_PAGE_RENDER, $script );
323+
324+
}
325+
326+
/**
327+
* @param array $form
328+
*
329+
* @return bool
330+
*/
331+
public function is_applicable_form( $form ) {
332+
333+
$form_id = isset( $form['id'] ) ? $form['id'] : $form;
334+
335+
return empty( $this->_args['form_id'] ) || (int) $form_id == (int) $this->_args['form_id'];
336+
}
337+
338+
/**
339+
* Check if the field is applicable for the current instance.
340+
*
341+
* @param int $field_id
342+
*
343+
* @return bool
344+
*/
345+
public function is_applicable_field( $field_id ) {
346+
$field_ids = isset( $this->_args['field_id'] ) ? $this->_args['field_id'] : array();
347+
348+
if ( empty( $field_ids ) ) {
349+
return true;
350+
}
351+
352+
if ( ! is_array( $field_ids ) ) {
353+
$field_ids = array( $field_ids );
354+
}
355+
356+
if ( in_array( $field_id, $field_ids, false ) ) {
357+
return true;
358+
}
359+
360+
return false;
361+
}
362+
363+
}
364+
365+
# Configuration
366+
367+
// Enable sorting for all Nested Form fields on all forms.
368+
new GPNF_Sortable_Entries();
369+
370+
// Enable sorting for Nested Form field with ID 1 on form with ID 8.
371+
//new GPNF_Sortable_Entries( array(
372+
// 'form_id' => 8,
373+
// 'field_id' => 1,
374+
//) );
375+
376+
// Enable sorting for all Nested Form fields on form with ID 8.
377+
//new GPNF_Sortable_Entries( array(
378+
// 'form_id' => 8,
379+
//) );
380+

0 commit comments

Comments
 (0)