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