PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.5.4
Secure Custom Fields v6.5.4
6.9.2 6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / src / js / _acf-field-relationship.js
secure-custom-fields / assets / src / js Last commit date
bindings 1 year ago commands 1 year ago pro 1 year ago _acf-compatibility.js 1 year ago _acf-condition-types.js 1 year ago _acf-condition.js 1 year ago _acf-conditions.js 1 year ago _acf-field-accordion.js 1 year ago _acf-field-button-group.js 1 year ago _acf-field-checkbox.js 1 year ago _acf-field-color-picker.js 1 year ago _acf-field-date-picker.js 1 year ago _acf-field-date-time-picker.js 1 year ago _acf-field-file.js 1 year ago _acf-field-google-map.js 1 year ago _acf-field-icon-picker.js 1 year ago _acf-field-image.js 1 year ago _acf-field-link.js 1 year ago _acf-field-oembed.js 1 year ago _acf-field-page-link.js 1 year ago _acf-field-post-object.js 1 year ago _acf-field-radio.js 1 year ago _acf-field-range.js 1 year ago _acf-field-relationship.js 1 year ago _acf-field-select.js 1 year ago _acf-field-tab.js 1 year ago _acf-field-taxonomy.js 1 year ago _acf-field-time-picker.js 1 year ago _acf-field-true-false.js 1 year ago _acf-field-url.js 1 year ago _acf-field-user.js 1 year ago _acf-field-wysiwyg.js 1 year ago _acf-field.js 1 year ago _acf-fields.js 1 year ago _acf-helpers.js 1 year ago _acf-hooks.js 1 year ago _acf-internal-post-type.js 1 year ago _acf-media.js 1 year ago _acf-modal.js 1 year ago _acf-model.js 1 year ago _acf-notice.js 1 year ago _acf-panel.js 1 year ago _acf-popup.js 1 year ago _acf-postbox.js 1 year ago _acf-screen.js 1 year ago _acf-select2.js 1 year ago _acf-tinymce.js 1 year ago _acf-tooltip.js 1 year ago _acf-unload.js 1 year ago _acf-validation.js 1 year ago _acf.js 1 year ago _browse-fields-modal.js 1 year ago _field-group-compatibility.js 1 year ago _field-group-conditions.js 1 year ago _field-group-field.js 1 year ago _field-group-fields.js 1 year ago _field-group-locations.js 1 year ago _field-group-settings.js 1 year ago _field-group.js 1 year ago acf-escaped-html-notice.js 1 year ago acf-field-group.js 1 year ago acf-input.js 1 year ago acf-internal-post-type.js 1 year ago acf.js 1 year ago
_acf-field-relationship.js
426 lines
1 ( function ( $, undefined ) {
2 var Field = acf.Field.extend( {
3 type: 'relationship',
4
5 events: {
6 'keypress [data-filter]': 'onKeypressFilter',
7 'change [data-filter]': 'onChangeFilter',
8 'keyup [data-filter]': 'onChangeFilter',
9 'click .choices-list .acf-rel-item': 'onClickAdd',
10 'keypress .choices-list .acf-rel-item': 'onKeypressFilter',
11 'keypress .values-list .acf-rel-item': 'onKeypressFilter',
12 'click [data-name="remove_item"]': 'onClickRemove',
13 'touchstart .values-list .acf-rel-item': 'onTouchStartValues',
14 },
15
16 $control: function () {
17 return this.$( '.acf-relationship' );
18 },
19
20 $list: function ( list ) {
21 return this.$( '.' + list + '-list' );
22 },
23
24 $listItems: function ( list ) {
25 return this.$list( list ).find( '.acf-rel-item' );
26 },
27
28 $listItem: function ( list, id ) {
29 return this.$list( list ).find(
30 '.acf-rel-item[data-id="' + id + '"]'
31 );
32 },
33
34 getValue: function () {
35 var val = [];
36 this.$listItems( 'values' ).each( function () {
37 val.push( $( this ).data( 'id' ) );
38 } );
39 return val.length ? val : false;
40 },
41
42 newChoice: function ( props ) {
43 return [
44 '<li>',
45 '<span tabindex="0" data-id="' +
46 props.id +
47 '" class="acf-rel-item">' +
48 props.text +
49 '</span>',
50 '</li>',
51 ].join( '' );
52 },
53
54 newValue: function ( props ) {
55 return [
56 '<li>',
57 '<input type="hidden" name="' +
58 this.getInputName() +
59 '[]" value="' +
60 props.id +
61 '" />',
62 '<span tabindex="0" data-id="' +
63 props.id +
64 '" class="acf-rel-item acf-rel-item-remove">' +
65 props.text,
66 '<a href="#" class="acf-icon -minus small dark" data-name="remove_item"></a>',
67 '</span>',
68 '</li>',
69 ].join( '' );
70 },
71
72 initialize: function () {
73 // Delay initialization until "interacted with" or "in view".
74 var delayed = this.proxy(
75 acf.once( function () {
76 // Add sortable.
77 this.$list( 'values' ).sortable( {
78 items: 'li',
79 forceHelperSize: true,
80 forcePlaceholderSize: true,
81 scroll: true,
82 update: this.proxy( function () {
83 this.$input().trigger( 'change' );
84 } ),
85 } );
86
87 // Avoid browser remembering old scroll position and add event.
88 this.$list( 'choices' )
89 .scrollTop( 0 )
90 .on( 'scroll', this.proxy( this.onScrollChoices ) );
91
92 // Fetch choices.
93 this.fetch();
94 } )
95 );
96
97 // Bind "interacted with".
98 this.$el.one( 'mouseover', delayed );
99 this.$el.one( 'focus', 'input', delayed );
100
101 // Bind "in view".
102 acf.onceInView( this.$el, delayed );
103 },
104
105 onScrollChoices: function ( e ) {
106 // bail early if no more results
107 if ( this.get( 'loading' ) || ! this.get( 'more' ) ) {
108 return;
109 }
110
111 // Scrolled to bottom
112 var $list = this.$list( 'choices' );
113 var scrollTop = Math.ceil( $list.scrollTop() );
114 var scrollHeight = Math.ceil( $list[ 0 ].scrollHeight );
115 var innerHeight = Math.ceil( $list.innerHeight() );
116 var paged = this.get( 'paged' ) || 1;
117 if ( scrollTop + innerHeight >= scrollHeight ) {
118 // update paged
119 this.set( 'paged', paged + 1 );
120
121 // fetch
122 this.fetch();
123 }
124 },
125
126 onKeypressFilter: function ( e, $el ) {
127 // Receive enter key when selecting relationship items.
128 if ( $el.hasClass( 'acf-rel-item-add' ) && e.which == 13 ) {
129 this.onClickAdd(e, $el);
130 }
131 // Receive enter key when removing relationship items.
132 if ( $el.hasClass( 'acf-rel-item-remove' ) && e.which == 13 ) {
133 this.onClickRemove(e, $el);
134 }
135 // don't submit form
136 if ( e.which == 13 ) {
137 e.preventDefault();
138 }
139 },
140
141 onChangeFilter: function ( e, $el ) {
142 // vars
143 var val = $el.val();
144 var filter = $el.data( 'filter' );
145
146 // Bail early if filter has not changed
147 if ( this.get( filter ) === val ) {
148 return;
149 }
150
151 // update attr
152 this.set( filter, val );
153
154 if ( filter === 's' ) {
155 // If val is numeric, limit results to include.
156 if ( parseInt( val ) ) {
157 this.set( 'include', val );
158 }
159 }
160
161 // reset paged
162 this.set( 'paged', 1 );
163
164 // fetch
165 if ( $el.is( 'select' ) ) {
166 this.fetch();
167
168 // search must go through timeout
169 } else {
170 this.maybeFetch();
171 }
172 },
173
174 onClickAdd: function ( e, $el ) {
175 // vars
176 var val = this.val();
177 var max = parseInt( this.get( 'max' ) );
178
179 // can be added?
180 if ( $el.hasClass( 'disabled' ) ) {
181 return false;
182 }
183
184 // validate
185 if ( max > 0 && val && val.length >= max ) {
186 // add notice
187 this.showNotice( {
188 text: acf
189 .__( 'Maximum values reached ( {max} values )' )
190 .replace( '{max}', max ),
191 type: 'warning',
192 } );
193 return false;
194 }
195
196 // disable
197 $el.addClass( 'disabled' );
198
199 // add
200 var html = this.newValue( {
201 id: $el.data( 'id' ),
202 text: $el.html(),
203 } );
204 this.$list( 'values' ).append( html );
205
206 // trigger change
207 this.$input().trigger( 'change' );
208 },
209
210 onClickRemove: function ( e, $el ) {
211 // Prevent default here because generic handler wont be triggered.
212 e.preventDefault();
213
214 let $span;
215 // Behavior if triggered from tabbed event.
216 if ( $el.hasClass( 'acf-rel-item-remove' )) {
217 $span = $el;
218 } else {
219 // Behavior if triggered through click event.
220 $span = $el.parent();
221 }
222
223 // vars
224 const $li = $span.parent();
225 const id = $span.data( 'id' );
226
227 // remove value
228 $li.remove();
229
230 // show choice
231 this.$listItem( 'choices', id ).removeClass( 'disabled' );
232
233 // trigger change
234 this.$input().trigger( 'change' );
235 },
236
237 onTouchStartValues: function( e, $el ) {
238 $( this.$listItems( 'values' ) ).removeClass( 'relationship-hover' );
239 $el.addClass( 'relationship-hover' );
240 },
241
242 maybeFetch: function () {
243 // vars
244 var timeout = this.get( 'timeout' );
245
246 // abort timeout
247 if ( timeout ) {
248 clearTimeout( timeout );
249 }
250
251 // fetch
252 timeout = this.setTimeout( this.fetch, 300 );
253 this.set( 'timeout', timeout );
254 },
255
256 getAjaxData: function () {
257 // load data based on element attributes
258 var ajaxData = this.$control().data();
259 for ( var name in ajaxData ) {
260 ajaxData[ name ] = this.get( name );
261 }
262
263 // extra
264 ajaxData.action = 'acf/fields/relationship/query';
265 ajaxData.field_key = this.get( 'key' );
266 ajaxData.nonce = this.get( 'nonce' );
267
268 // Filter.
269 ajaxData = acf.applyFilters(
270 'relationship_ajax_data',
271 ajaxData,
272 this
273 );
274
275 // return
276 return ajaxData;
277 },
278
279 fetch: function () {
280 // abort XHR if this field is already loading AJAX data
281 var xhr = this.get( 'xhr' );
282 if ( xhr ) {
283 xhr.abort();
284 }
285
286 // add to this.o
287 var ajaxData = this.getAjaxData();
288
289 // clear html if is new query
290 var $choiceslist = this.$list( 'choices' );
291 if ( ajaxData.paged == 1 ) {
292 $choiceslist.html( '' );
293 }
294
295 // loading
296 var $loading = $(
297 '<li><i class="acf-loading"></i> ' +
298 acf.__( 'Loading' ) +
299 '</li>'
300 );
301 $choiceslist.append( $loading );
302 this.set( 'loading', true );
303
304 // callback
305 var onComplete = function () {
306 this.set( 'loading', false );
307 $loading.remove();
308 };
309
310 var onSuccess = function ( json ) {
311 // no results
312 if ( ! json || ! json.results || ! json.results.length ) {
313 // prevent pagination
314 this.set( 'more', false );
315
316 // add message
317 if ( this.get( 'paged' ) == 1 ) {
318 this.$list( 'choices' ).append(
319 '<li>' + acf.__( 'No matches found' ) + '</li>'
320 );
321 }
322
323 // return
324 return;
325 }
326
327 // set more (allows pagination scroll)
328 this.set( 'more', json.more );
329
330 // get new results
331 var html = this.walkChoices( json.results );
332 var $html = $( html );
333
334 // apply .disabled to left li's
335 var val = this.val();
336 if ( val && val.length ) {
337 val.map( function ( id ) {
338 $html
339 .find( '.acf-rel-item[data-id="' + id + '"]' )
340 .addClass( 'disabled' );
341 } );
342 }
343
344 // append
345 $choiceslist.append( $html );
346
347 // merge together groups
348 var $prevLabel = false;
349 var $prevList = false;
350
351 $choiceslist.find( '.acf-rel-label' ).each( function () {
352 var $label = $( this );
353 var $list = $label.siblings( 'ul' );
354
355 if ( $prevLabel && $prevLabel.text() == $label.text() ) {
356 $prevList.append( $list.children() );
357 $( this ).parent().remove();
358 return;
359 }
360
361 // update vars
362 $prevLabel = $label;
363 $prevList = $list;
364 } );
365 };
366
367 // get results
368 var xhr = $.ajax( {
369 url: acf.get( 'ajaxurl' ),
370 dataType: 'json',
371 type: 'post',
372 data: acf.prepareForAjax( ajaxData ),
373 context: this,
374 success: onSuccess,
375 complete: onComplete,
376 } );
377
378 // set
379 this.set( 'xhr', xhr );
380 },
381
382 walkChoices: function ( data ) {
383 // walker
384 var walk = function ( data ) {
385 // vars
386 var html = '';
387
388 // is array
389 if ( $.isArray( data ) ) {
390 data.map( function ( item ) {
391 html += walk( item );
392 } );
393
394 // is item
395 } else if ( $.isPlainObject( data ) ) {
396 // group
397 if ( data.children !== undefined ) {
398 html +=
399 '<li><span class="acf-rel-label">' +
400 acf.escHtml( data.text ) +
401 '</span><ul class="acf-bl">';
402 html += walk( data.children );
403 html += '</ul></li>';
404
405 // single
406 } else {
407 html +=
408 '<li><span tabindex="0" class="acf-rel-item acf-rel-item-add" data-id="' +
409 acf.escAttr( data.id ) +
410 '">' +
411 acf.escHtml( data.text ) +
412 '</span></li>';
413 }
414 }
415
416 // return
417 return html;
418 };
419
420 return walk( data );
421 },
422 } );
423
424 acf.registerFieldType( Field );
425 } )( jQuery );
426