├── cmb2-field-ajax-search.php
├── css
└── ajax-search.css
├── example.gif
├── js
├── ajax-search.js
└── jquery.autocomplete.min.js
└── readme.md
/cmb2-field-ajax-search.php:
--------------------------------------------------------------------------------
1 | convert_as_id_css($field->_name());
66 | $default_limit = 1;
67 |
68 | // Current filter is cmb2_render_{$object_to_search}_ajax_search ( post, user or term )
69 | $object_to_search = str_replace( 'cmb2_render_', '', str_replace( '_ajax_search', '', current_filter() ) );
70 |
71 | if( ! is_array( $value ) && strpos( $value, ', ' ) ) {
72 | $value = explode(', ', $value);
73 | }
74 |
75 | if( $field->args( 'multiple-item' ) == true ) {
76 | $default_limit = -1; // 0 or -1 means unlimited
77 |
78 | ?>
input( array(
108 | 'type' => 'hidden',
109 | 'name' => $field->_name(),
110 | 'value' => $value,
111 | 'desc' => false
112 | ) );
113 |
114 | $input_value = ( $value ? $this->object_text( $field_name, $value, $object_to_search ) : '' );
115 | }
116 |
117 | echo $field_type->input( array(
118 | 'type' => 'text',
119 | 'name' => '_' . $field->_name(),
120 | 'id' => $field_name,
121 | 'class' => 'cmb-ajax-search cmb-' . $object_to_search . '-ajax-search',
122 | 'value' => $input_value,
123 | 'desc' => false,
124 | 'data-multiple' => $field->args( 'multiple-item' ) ? $field->args( 'multiple-item' ) : '0',
125 | 'data-limit' => $field->args( 'limit' ) ? $field->args( 'limit' ) : $default_limit,
126 | 'data-sortable' => $field->args( 'sortable' ) ? $field->args( 'sortable' ) : '0',
127 | 'data-object-type' => $object_to_search,
128 | 'data-query-args' => $field->args( 'query_args' ) ? htmlspecialchars( json_encode( $field->args( 'query_args' ) ), ENT_QUOTES, 'UTF-8' ) : ''
129 | ) );
130 |
131 | echo '
';
132 |
133 | $field_type->_desc( true, true );
134 |
135 | }
136 |
137 | /**
138 | * Display field
139 | */
140 | public function display( $pre_output, $field, $display ) {
141 | $object_type = str_replace( 'cmb2_pre_field_display_', '', str_replace( '_ajax_search', '', current_filter() ) );
142 |
143 | ob_start();
144 |
145 | $field->peform_param_callback( 'before_display_wrap' );
146 |
147 | printf( "";
166 |
167 | $field->peform_param_callback( 'after_display_wrap' );
168 |
169 | $pre_output = ob_get_clean();
170 |
171 | return $pre_output;
172 | }
173 |
174 | /**
175 | * Optionally save the latitude/longitude values into two custom fields
176 | */
177 | public function sanitize( $override_value, $value, $object_id, $field_args ) {
178 | if ( !is_array( $value ) || !( array_key_exists('repeatable', $field_args ) && $field_args['repeatable'] == TRUE ) ) {
179 | return $override_value;
180 | }
181 |
182 | $new_values = array();
183 | foreach ( $value as $key => $val ) {
184 | $new_values[$key] = array_filter( array_map( 'sanitize_text_field', $val ) );
185 | }
186 |
187 | return array_filter( array_values( $new_values ) );
188 | }
189 |
190 | /**
191 | * Enqueue scripts and styles
192 | */
193 | public function setup_admin_scripts() {
194 |
195 | wp_register_script( 'jquery-autocomplete-ajax-search', plugins_url( 'js/jquery.autocomplete.min.js', __FILE__ ), array( 'jquery' ), self::VERSION, true );
196 | wp_register_script( 'cmb-ajax-search', plugins_url( 'js/ajax-search.js', __FILE__ ), array( 'jquery', 'jquery-autocomplete-ajax-search', 'jquery-ui-sortable' ), self::VERSION, true );
197 |
198 | wp_localize_script( 'cmb-ajax-search', 'cmb_ajax_search', array(
199 | 'ajaxurl' => admin_url( 'admin-ajax.php' ),
200 | 'nonce' => wp_create_nonce( 'cmb_ajax_search_get_results' ),
201 | 'options' => apply_filters( 'cmb_field_ajax_search_autocomplete_options', array() )
202 | ) );
203 |
204 | wp_enqueue_script( 'cmb-ajax-search' );
205 | wp_enqueue_style( 'cmb-ajax-search', plugins_url( 'css/ajax-search.css', __FILE__ ), array(), self::VERSION );
206 |
207 | }
208 |
209 | /**
210 | * Ajax request : get results
211 | */
212 | public function get_results() {
213 | $nonce = $_POST['nonce'];
214 |
215 | if ( ! wp_verify_nonce( $nonce, 'cmb_ajax_search_get_results' ) ) {
216 | // Wrong nonce
217 | die( json_encode( array(
218 | 'error' => __( 'Error : Unauthorized action' )
219 | ) ) );
220 | } else if ( ( ! isset( $_POST['field_id'] ) || empty( $_POST['field_id'] ) )
221 | || ( ! isset( $_POST['object_type'] ) || empty( $_POST['object_type'] ) ) ) {
222 | // Wrong request parameters (field_id and object_type are mandatory)
223 | die( json_encode( array(
224 | 'error' => __( 'Error : Wrong request parameters' )
225 | ) ) );
226 | } else {
227 | $query_args = json_decode( stripslashes( htmlspecialchars_decode( $_POST['query_args'] ) ), true );
228 | $data = array();
229 | $results = array();
230 |
231 | switch( $_POST['object_type'] ) {
232 | case 'post':
233 | $query_args['s'] = $_POST['query'];
234 | $query = new WP_Query( $query_args );
235 | $results = $query->posts;
236 | break;
237 | case 'user':
238 | $query_args['search'] = '*' . $_POST['query'] . '*';
239 | $query = new WP_User_Query( $query_args );
240 | $results = $query->results;
241 | break;
242 | case 'term':
243 | $query_args['search'] = $_POST['query'];
244 | $query = new WP_Term_Query( $query_args );
245 | $results = $query->terms;
246 | break;
247 | }
248 |
249 | foreach ( $results as $result ) :
250 | if( $_POST['object_type'] == 'term' ) {
251 | $result_id = $result->term_id;
252 | } else {
253 | $result_id = $result->ID;
254 | }
255 |
256 | $data[] = array(
257 | 'id' => $result_id,
258 | 'value' => $this->object_text( $_POST['field_id'], $result_id, $_POST['object_type'] ),
259 | 'link' => $this->object_link( $_POST['field_id'], $result_id, $_POST['object_type'] )
260 | );
261 | endforeach;
262 |
263 | wp_send_json( $data );
264 | exit;
265 | }
266 | }
267 |
268 | public function object_text( $field_id, $object_id, $object_type ) {
269 | $text = '';
270 |
271 | if( $object_type == 'post' ) {
272 | $text = get_the_title( $object_id );
273 | } else if( $object_type == 'user' ) {
274 | $text = get_the_author_meta('display_name', $object_id);
275 | } else if( $object_type == 'term' ) {
276 | $term = get_term( $object_id );
277 |
278 | $text = $term->name;
279 | }
280 |
281 | $text = apply_filters( "cmb_{$field_id}_ajax_search_result_text", $text, $object_id, $object_type );
282 |
283 | return $text;
284 | }
285 |
286 | public function object_link( $field_id, $object_id, $object_type ) {
287 | $link = '#';
288 |
289 | if( $object_type == 'post' ) {
290 | $link = get_edit_post_link( $object_id );
291 | } else if( $object_type == 'user' ) {
292 | $link = get_edit_user_link( $object_id );
293 | } else if( $object_type == 'term' ) {
294 | $link = get_edit_term_link( $object_id );
295 | }
296 |
297 | $link = apply_filters( "cmb_{$field_id}_ajax_search_result_link", $link, $object_id, $object_type );
298 |
299 | return $link;
300 | }
301 |
302 | }
303 |
304 | $cmb2_field_ajax_search = new CMB2_Field_Ajax_Search();
305 |
306 | }
307 |
308 |
--------------------------------------------------------------------------------
/css/ajax-search.css:
--------------------------------------------------------------------------------
1 | .autocomplete-suggestions { border: 1px solid #DDD; background: #FFF; overflow: auto; box-sizing: border-box; }
2 | .autocomplete-suggestion { padding: 5px 5px; white-space: nowrap; overflow: hidden; cursor: pointer; }
3 | .autocomplete-no-suggestion { padding: 5px 5px; white-space: nowrap; overflow: hidden; }
4 | .autocomplete-selected { background: #F0F0F0; }
5 | .autocomplete-suggestions strong { font-weight: normal; color: #3399FF; }
6 | .autocomplete-group { padding: 2px 5px; }
7 | .autocomplete-group strong { display: block; border-bottom: 1px solid #000; }
8 |
9 | input.cmb-ajax-search { width: 25em; }
10 | .cmb-ajax-search-results { width: 25em; padding: 0 1px; box-sizing: border-box; margin-bottom: 5px; }
11 | .cmb-ajax-search-results li { margin: 0; border-bottom: 1px solid #f4f4f4; padding: 7px 5px; background: #fff; }
12 | .cmb-ajax-search-results li:last-child { border-bottom: none; }
13 | .cmb-ajax-search-results li span.hndl { font-size: 13px; line-height: 16px; color: #ccc; margin: 0 8px 0 0; height: 16px; cursor: s-resize; }
14 | .cmb-ajax-search-results li span.hndl:before { content: '\2807'; }
15 | .cmb-ajax-search-results li a.edit-link { color: #333; text-decoration: none; }
16 | .cmb-ajax-search-results li a.edit-link:hover { color: #00a0d2; text-decoration: underline; }
17 | .cmb-ajax-search-results li a.remover { float: right; cursor:pointer; color: #ccc; }
18 | .cmb-ajax-search-results li a.remover span { height: 16px; line-height: 16px; font-size: 14px; }
19 | .cmb-ajax-search-results li a.remover span.dashicons-dismiss { display: none; }
20 | .cmb-ajax-search-results li a.remover:hover { color: #C00; }
21 | .cmb-ajax-search-results li a.remover:hover span.dashicons-dismiss { display: inline-block; }
22 | .cmb-ajax-search-results li a.remover:hover span.dashicons-no { display: none; }
23 | .cmb-ajax-search-results .ui-state-highlight { background: #f4f4f4; }
24 | .cmb-ajax-search-spinner { position: absolute; margin: 9px 0 0 -25px; width: 15px; display:none; }
25 |
26 | .columns-2 #postbox-container-1 .cmb-ajax-search-spinner { right: 25px; margin: -23px 0 0 0; }
27 |
28 | #side-sortables .cmb2-wrap input + input.cmb-ajax-search { margin-top: 1px; } /* Fix extra margin from CMB2 */
29 |
30 | /* Repeatable groups */
31 | .cmb-repeatable-group .cmb-type-post-ajax-search .cmb-td, .cmb-repeatable-group .cmb-type-user-ajax-search .cmb-td, .cmb-repeatable-group .cmb-type-term-ajax-search .cmb-td { position: relative; }
32 | .cmb-repeatable-group .cmb-ajax-search-spinner { position: absolute; top: 50%; right: 10px; margin: -7px 0 0 0; }
33 |
--------------------------------------------------------------------------------
/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubengc/cmb2-field-ajax-search/d6ce6047ff6f292e10fa6980d8e249b66261e576/example.gif
--------------------------------------------------------------------------------
/js/ajax-search.js:
--------------------------------------------------------------------------------
1 | (function( document, $ ) {
2 | function init_ajax_search() {
3 | $('.cmb-ajax-search:not([data-ajax-search="true"])').each(function () {
4 | $(this).attr('data-ajax-search', true);
5 |
6 | var field_id = $(this).attr('id').replace(/[\[\]']+/g, '_'); // Field id, the true one field
7 | var object_type = $(this).attr('data-object-type');
8 | var query_args = $(this).attr('data-query-args');
9 |
10 | $(this).devbridgeAutocomplete(Object.assign({
11 | serviceUrl: cmb_ajax_search.ajaxurl,
12 | type: 'POST',
13 | triggerSelectOnValidInput: false,
14 | showNoSuggestionNotice: true,
15 | params: {
16 | action : 'cmb_ajax_search_get_results',
17 | nonce : cmb_ajax_search.nonce, // nonce
18 | field_id : field_id, // Field id for hook purposes
19 | object_type : object_type, // post, user or term
20 | query_args : query_args, // Query args passed to field
21 | },
22 | transformResult: function( results ) {
23 | var suggestions = $.parseJSON( results );
24 |
25 | if( $('#' + field_id + '_results li').length ) {
26 | var selected_vals = [];
27 | var d = 0;
28 |
29 | $('#' + field_id + '_results input').each(function( index, element ) {
30 | selected_vals.push( $(this).val() );
31 | });
32 |
33 | // Remove already picked suggestions
34 | $(suggestions).each(function( index, suggestion ) {
35 | if( $.inArray( ( suggestion.id ).toString(), selected_vals ) > -1 ) {
36 | suggestions.splice( index - d, 1 );
37 | d++;
38 | }
39 | });
40 | }
41 |
42 | return { suggestions: suggestions };
43 | },
44 | onSearchStart: function(){
45 | $(this).next('img.cmb-ajax-search-spinner').css( 'display', 'inline-block' );
46 | },
47 | onSearchComplete: function(){
48 | $(this).next('img.cmb-ajax-search-spinner').hide();
49 | },
50 | onSelect: function ( suggestion ) {
51 | $(this).devbridgeAutocomplete('clearCache');
52 |
53 | var field_name = $(this).attr('name');
54 | var multiple = $(this).attr('data-multiple');
55 | var limit = parseInt( $(this).attr('data-limit') );
56 | var sortable = $(this).attr('data-sortable');
57 | var field_name_temp = field_name.substring(1).replace( /[\[\]']+/g, '_' );
58 |
59 | if( multiple == 1 ) {
60 | // Multiple
61 | $('#' + field_name_temp + '_results' ).append( '' +
62 | ( ( sortable == 1 ) ? '' : '' ) +
63 | '' +
64 | '' + suggestion.value + '' +
65 | '' +
66 | '' );
67 |
68 | $(this).val( '' );
69 |
70 | // Checks if there is the max allowed results, limit < 0 means unlimited
71 | if( limit > 0 && limit == $('#' + field_name_temp + '_results li').length ) {
72 | $(this).prop( 'disabled', 'disabled' );
73 | } else {
74 | $(this).focus();
75 | }
76 | } else {
77 | // Singular
78 | $('input[name="' + field_name + '"]').val( suggestion.id ).change();
79 | }
80 | }
81 | },
82 | cmb_ajax_search.options));
83 |
84 | if( $(this).attr('data-sortable') == 1 ){
85 | $('#' + field_id + '_results').sortable({
86 | handle : '.hndl',
87 | placeholder : 'ui-state-highlight',
88 | forcePlaceholderSize : true
89 | });
90 | }
91 | });
92 | }
93 |
94 | // Initialize ajax search
95 | init_ajax_search();
96 |
97 | // Initialize on group fields add row
98 | $( document ).on( 'cmb2_add_row', function( evt, $row ) {
99 | $row.find('.cmb-ajax-search').attr('data-ajax-search', false);
100 |
101 | init_ajax_search();
102 | });
103 |
104 | // Initialize on widgets area
105 | $(document).on('widget-updated widget-added', function() {
106 | init_ajax_search();
107 | });
108 |
109 | // On click remover listener
110 | $('body').on( 'click', '.cmb-ajax-search-results a.remover', function() {
111 | $(this).parent('li').fadeOut( 400, function(){
112 | var field_id = $(this).parents('ul').attr('id').replace('_results', '');
113 |
114 | $('#' + field_id).removeProp( 'disabled' );
115 | $('#' + field_id).devbridgeAutocomplete('clearCache');
116 |
117 | $(this).remove();
118 | });
119 | });
120 | })(document, jQuery);
121 |
--------------------------------------------------------------------------------
/js/jquery.autocomplete.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Ajax Autocomplete for jQuery, version 1.4.3
3 | * (c) 2017 Tomas Kirda
4 | *
5 | * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
6 | * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
7 | */
8 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports&&"function"==typeof require?require("jquery"):jQuery)}(function(a){"use strict";function b(c,d){var e=this;e.element=c,e.el=a(c),e.suggestions=[],e.badQueries=[],e.selectedIndex=-1,e.currentValue=e.element.value,e.timeoutId=null,e.cachedResponse={},e.onChangeTimeout=null,e.onChange=null,e.isLocal=!1,e.suggestionsContainer=null,e.noSuggestionsContainer=null,e.options=a.extend({},b.defaults,d),e.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"},e.hint=null,e.hintValue="",e.selection=null,e.initialize(),e.setOptions(d)}function c(a,b,c){return a.value.toLowerCase().indexOf(c)!==-1}function d(b){return"string"==typeof b?a.parseJSON(b):b}function e(a,b){if(!b)return a.value;var c="("+g.escapeRegExChars(b)+")";return a.value.replace(new RegExp(c,"gi"),"$1").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/<(\/?strong)>/g,"<$1>")}function f(a,b){return''+b+"
"}var g=function(){return{escapeRegExChars:function(a){return a.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");return b.className=a,b.style.position="absolute",b.style.display="none",b}}}(),h={ESC:27,TAB:9,RETURN:13,LEFT:37,UP:38,RIGHT:39,DOWN:40},i=a.noop;b.utils=g,a.Autocomplete=b,b.defaults={ajaxSettings:{},autoSelectFirst:!1,appendTo:"body",serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:e,formatGroup:f,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:i,onSearchComplete:i,onSearchError:i,preserveInput:!1,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,preventBadQueries:!0,lookupFilter:c,paramName:"query",transformResult:d,showNoSuggestionNotice:!1,noSuggestionNotice:"No results",orientation:"bottom",forceFixPosition:!1},b.prototype={initialize:function(){var c,d=this,e="."+d.classes.suggestion,f=d.classes.selected,g=d.options;d.element.setAttribute("autocomplete","off"),d.noSuggestionsContainer=a('').html(this.options.noSuggestionNotice).get(0),d.suggestionsContainer=b.utils.createNode(g.containerClass),c=a(d.suggestionsContainer),c.appendTo(g.appendTo||"body"),"auto"!==g.width&&c.css("width",g.width),c.on("mouseover.autocomplete",e,function(){d.activate(a(this).data("index"))}),c.on("mouseout.autocomplete",function(){d.selectedIndex=-1,c.children("."+f).removeClass(f)}),c.on("click.autocomplete",e,function(){d.select(a(this).data("index"))}),c.on("click.autocomplete",function(){clearTimeout(d.blurTimeoutId)}),d.fixPositionCapture=function(){d.visible&&d.fixPosition()},a(window).on("resize.autocomplete",d.fixPositionCapture),d.el.on("keydown.autocomplete",function(a){d.onKeyPress(a)}),d.el.on("keyup.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("blur.autocomplete",function(){d.onBlur()}),d.el.on("focus.autocomplete",function(){d.onFocus()}),d.el.on("change.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("input.autocomplete",function(a){d.onKeyUp(a)})},onFocus:function(){var a=this;a.fixPosition(),a.el.val().length>=a.options.minChars&&a.onValueChange()},onBlur:function(){var a=this;a.blurTimeoutId=setTimeout(function(){a.hide()},200)},abortAjax:function(){var a=this;a.currentRequest&&(a.currentRequest.abort(),a.currentRequest=null)},setOptions:function(b){var c=this,d=a.extend({},c.options,b);c.isLocal=Array.isArray(d.lookup),c.isLocal&&(d.lookup=c.verifySuggestionsFormat(d.lookup)),d.orientation=c.validateOrientation(d.orientation,"bottom"),a(c.suggestionsContainer).css({"max-height":d.maxHeight+"px",width:d.width+"px","z-index":d.zIndex}),this.options=d},clearCache:function(){this.cachedResponse={},this.badQueries=[]},clear:function(){this.clearCache(),this.currentValue="",this.suggestions=[]},disable:function(){var a=this;a.disabled=!0,clearTimeout(a.onChangeTimeout),a.abortAjax()},enable:function(){this.disabled=!1},fixPosition:function(){var b=this,c=a(b.suggestionsContainer),d=c.parent().get(0);if(d===document.body||b.options.forceFixPosition){var e=b.options.orientation,f=c.outerHeight(),g=b.el.outerHeight(),h=b.el.offset(),i={top:h.top,left:h.left};if("auto"===e){var j=a(window).height(),k=a(window).scrollTop(),l=-k+h.top-f,m=k+j-(h.top+g+f);e=Math.max(l,m)===l?"top":"bottom"}if("top"===e?i.top+=-f:i.top+=g,d!==document.body){var n,o=c.css("opacity");b.visible||c.css("opacity",0).show(),n=c.offsetParent().offset(),i.top-=n.top,i.left-=n.left,b.visible||c.css("opacity",o).hide()}"auto"===b.options.width&&(i.width=b.el.outerWidth()+"px"),c.css(i)}},isCursorAtEnd:function(){var a,b=this,c=b.el.val().length,d=b.element.selectionStart;return"number"==typeof d?d===c:!document.selection||(a=document.selection.createRange(),a.moveStart("character",-c),c===a.text.length)},onKeyPress:function(a){var b=this;if(!b.disabled&&!b.visible&&a.which===h.DOWN&&b.currentValue)return void b.suggest();if(!b.disabled&&b.visible){switch(a.which){case h.ESC:b.el.val(b.currentValue),b.hide();break;case h.RIGHT:if(b.hint&&b.options.onHint&&b.isCursorAtEnd()){b.selectHint();break}return;case h.TAB:if(b.hint&&b.options.onHint)return void b.selectHint();if(b.selectedIndex===-1)return void b.hide();if(b.select(b.selectedIndex),b.options.tabDisabled===!1)return;break;case h.RETURN:if(b.selectedIndex===-1)return void b.hide();b.select(b.selectedIndex);break;case h.UP:b.moveUp();break;case h.DOWN:b.moveDown();break;default:return}a.stopImmediatePropagation(),a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case h.UP:case h.DOWN:return}clearTimeout(b.onChangeTimeout),b.currentValue!==b.el.val()&&(b.findBestHint(),b.options.deferRequestBy>0?b.onChangeTimeout=setTimeout(function(){b.onValueChange()},b.options.deferRequestBy):b.onValueChange())}},onValueChange:function(){var b=this,c=b.options,d=b.el.val(),e=b.getQuery(d);return b.selection&&b.currentValue!==e&&(b.selection=null,(c.onInvalidateSelection||a.noop).call(b.element)),clearTimeout(b.onChangeTimeout),b.currentValue=d,b.selectedIndex=-1,c.triggerSelectOnValidInput&&b.isExactMatch(e)?void b.select(0):void(e.lengthh&&(c.suggestions=c.suggestions.slice(0,h)),c},getSuggestions:function(b){var c,d,e,f,g=this,h=g.options,i=h.serviceUrl;if(h.params[h.paramName]=b,h.onSearchStart.call(g.element,h.params)!==!1){if(d=h.ignoreParams?null:h.params,a.isFunction(h.lookup))return void h.lookup(b,function(a){g.suggestions=a.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,a.suggestions)});g.isLocal?c=g.getSuggestionsLocal(b):(a.isFunction(i)&&(i=i.call(g.element,b)),e=i+"?"+a.param(d||{}),c=g.cachedResponse[e]),c&&Array.isArray(c.suggestions)?(g.suggestions=c.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,c.suggestions)):g.isBadQuery(b)?h.onSearchComplete.call(g.element,b,[]):(g.abortAjax(),f={url:i,data:d,type:h.type,dataType:h.dataType},a.extend(f,h.ajaxSettings),g.currentRequest=a.ajax(f).done(function(a){var c;g.currentRequest=null,c=h.transformResult(a,b),g.processResponse(c,b,e),h.onSearchComplete.call(g.element,b,c.suggestions)}).fail(function(a,c,d){h.onSearchError.call(g.element,b,a,c,d)}))}},isBadQuery:function(a){if(!this.options.preventBadQueries)return!1;for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){var b=this,c=a(b.suggestionsContainer);a.isFunction(b.options.onHide)&&b.visible&&b.options.onHide.call(b.element,c),b.visible=!1,b.selectedIndex=-1,clearTimeout(b.onChangeTimeout),a(b.suggestionsContainer).hide(),b.signalHint(null)},suggest:function(){if(!this.suggestions.length)return void(this.options.showNoSuggestionNotice?this.noSuggestions():this.hide());var b,c=this,d=c.options,e=d.groupBy,f=d.formatResult,g=c.getQuery(c.currentValue),h=c.classes.suggestion,i=c.classes.selected,j=a(c.suggestionsContainer),k=a(c.noSuggestionsContainer),l=d.beforeRender,m="",n=function(a,c){var f=a.data[e];return b===f?"":(b=f,d.formatGroup(a,b))};return d.triggerSelectOnValidInput&&c.isExactMatch(g)?void c.select(0):(a.each(c.suggestions,function(a,b){e&&(m+=n(b,g,a)),m+=''+f(b,g,a)+"
"}),this.adjustContainerWidth(),k.detach(),j.html(m),a.isFunction(l)&&l.call(c.element,j,c.suggestions),c.fixPosition(),j.show(),d.autoSelectFirst&&(c.selectedIndex=0,j.scrollTop(0),j.children("."+h).first().addClass(i)),c.visible=!0,void c.findBestHint())},noSuggestions:function(){var b=this,c=b.options.beforeRender,d=a(b.suggestionsContainer),e=a(b.noSuggestionsContainer);this.adjustContainerWidth(),e.detach(),d.empty(),d.append(e),a.isFunction(c)&&c.call(b.element,d,b.suggestions),b.fixPosition(),d.show(),b.visible=!0},adjustContainerWidth:function(){var b,c=this,d=c.options,e=a(c.suggestionsContainer);"auto"===d.width?(b=c.el.outerWidth(),e.css("width",b>0?b:300)):"flex"===d.width&&e.css("width","")},findBestHint:function(){var b=this,c=b.el.val().toLowerCase(),d=null;c&&(a.each(b.suggestions,function(a,b){var e=0===b.value.toLowerCase().indexOf(c);return e&&(d=b),!e}),b.signalHint(d))},signalHint:function(b){var c="",d=this;b&&(c=d.currentValue+b.value.substr(d.currentValue.length)),d.hintValue!==c&&(d.hintValue=c,d.hint=b,(this.options.onHint||a.noop)(c))},verifySuggestionsFormat:function(b){return b.length&&"string"==typeof b[0]?a.map(b,function(a){return{value:a,data:null}}):b},validateOrientation:function(b,c){return b=a.trim(b||"").toLowerCase(),a.inArray(b,["auto","bottom","top"])===-1&&(b=c),b},processResponse:function(a,b,c){var d=this,e=d.options;a.suggestions=d.verifySuggestionsFormat(a.suggestions),e.noCache||(d.cachedResponse[c]=a,e.preventBadQueries&&!a.suggestions.length&&d.badQueries.push(b)),b===d.getQuery(d.currentValue)&&(d.suggestions=a.suggestions,d.suggest())},activate:function(b){var c,d=this,e=d.classes.selected,f=a(d.suggestionsContainer),g=f.find("."+d.classes.suggestion);return f.find("."+e).removeClass(e),d.selectedIndex=b,d.selectedIndex!==-1&&g.length>d.selectedIndex?(c=g.get(d.selectedIndex),a(c).addClass(e),c):null},selectHint:function(){var b=this,c=a.inArray(b.hint,b.suggestions);b.select(c)},select:function(a){var b=this;b.hide(),b.onSelect(a)},moveUp:function(){var b=this;if(b.selectedIndex!==-1)return 0===b.selectedIndex?(a(b.suggestionsContainer).children().first().removeClass(b.classes.selected),b.selectedIndex=-1,b.el.val(b.currentValue),void b.findBestHint()):void b.adjustScroll(b.selectedIndex-1)},moveDown:function(){var a=this;a.selectedIndex!==a.suggestions.length-1&&a.adjustScroll(a.selectedIndex+1)},adjustScroll:function(b){var c=this,d=c.activate(b);if(d){var e,f,g,h=a(d).outerHeight();e=d.offsetTop,f=a(c.suggestionsContainer).scrollTop(),g=f+c.options.maxHeight-h,eg&&a(c.suggestionsContainer).scrollTop(e-c.options.maxHeight+h),c.options.preserveInput||c.el.val(c.getValue(c.suggestions[b].value)),c.signalHint(null)}},onSelect:function(b){var c=this,d=c.options.onSelect,e=c.suggestions[b];c.currentValue=c.getValue(e.value),c.currentValue===c.el.val()||c.options.preserveInput||c.el.val(c.currentValue),c.signalHint(null),c.suggestions=[],c.selection=e,a.isFunction(d)&&d.call(c.element,e)},getValue:function(a){var b,c,d=this,e=d.options.delimiter;return e?(b=d.currentValue,c=b.split(e),1===c.length?a:b.substr(0,b.length-c[c.length-1].length)+a):a},dispose:function(){var b=this;b.el.off(".autocomplete").removeData("autocomplete"),a(window).off("resize.autocomplete",b.fixPositionCapture),a(b.suggestionsContainer).remove()}},a.fn.devbridgeAutocomplete=function(c,d){var e="autocomplete";return arguments.length?this.each(function(){var f=a(this),g=f.data(e);"string"==typeof c?g&&"function"==typeof g[c]&&g[c](d):(g&&g.dispose&&g.dispose(),g=new b(this,c),f.data(e,g))}):this.first().data(e)},a.fn.autocomplete||(a.fn.autocomplete=a.fn.devbridgeAutocomplete)});
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | CMB2 Field Type: Ajax Search
2 | ==================
3 |
4 | Custom fields for [CMB2](https://github.com/WebDevStudios/CMB2) to attach posts, users or terms to each others.
5 |
6 | 
7 |
8 | Once activated, this plugin adds three new field types `post_ajax_search`, `user_ajax_search` and `term_ajax_search`.
9 |
10 | This plugin is an update of [CMB2 Field Type: Post Search Ajax](https://github.com/alexis-magina/cmb2-field-post-search-ajax) by [Magina](http://magina.fr/) with support to attach posts, users or terms.
11 |
12 | ## Installation
13 |
14 | You can install this field type as you would a WordPress plugin:
15 |
16 | - Download the plugin
17 | - Place the plugin folder in your /wp-content/plugins/ directory
18 | - Activate the plugin in the Plugin dashboard
19 |
20 | ## Parameters
21 |
22 | Options :
23 | - multiple (bool, default = false) : Turn field into a multiple attached objects
24 | - limit (int, default = -1 : single selection) : Limit the number of posts that can be selected (-1 for unlimited)
25 | - sortable (bool, default = false) : Allow selected items to be sort (only if multiple = true)
26 | - query_args (array) : Query arguments to pass on each request
27 |
28 | Query args:
29 | - query_args accepts same parameters as [WP_Query](https://codex.wordpress.org/Class_Reference/WP_Query) for `post_ajax_search`
30 | - query_args accepts same parameters as [WP_User_Query](https://codex.wordpress.org/Class_Reference/WP_User_Query) for `user_ajax_search`
31 | - query_args accepts same parameters as [WP_Term_Query](https://developer.wordpress.org/reference/classes/wp_term_query/) for `term_ajax_search`
32 |
33 | ## Examples
34 |
35 | ```php
36 | add_action( 'cmb2_admin_init', 'cmb2_ajax_search_metabox' );
37 | function cmb2_ajax_search_metabox() {
38 |
39 | $prefix = 'your_prefix_demo_';
40 |
41 | $cmb_demo = new_cmb2_box( array(
42 | 'id' => $prefix . 'metabox',
43 | 'title' => __( 'Attached posts Metabox', 'cmb2' ),
44 | 'object_types' => array( 'page', 'post' ), // Post type
45 | ) );
46 |
47 | // Single post
48 | $cmb_demo->add_field( array(
49 | 'name' => __( 'Attached post', 'cmb2' ),
50 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
51 | 'id' => $prefix . 'post',
52 | 'type' => 'post_ajax_search',
53 | 'query_args' => array(
54 | 'post_type' => array( 'post' ),
55 | 'posts_per_page' => -1
56 | )
57 | ) );
58 |
59 | // Multiple posts
60 | $cmb_demo->add_field( array(
61 | 'name' => __( 'Multiple posts', 'cmb2' ),
62 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
63 | 'id' => $prefix . 'posts',
64 | 'type' => 'post_ajax_search',
65 | 'multiple-item' => true,
66 | 'limit' => 10,
67 | 'query_args' => array(
68 | 'post_type' => array( 'post', 'page' ),
69 | 'post_status' => array( 'publish', 'pending' )
70 | )
71 | ) );
72 |
73 | // Single user
74 | $cmb_demo->add_field( array(
75 | 'name' => __( 'Attached user', 'cmb2' ),
76 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
77 | 'id' => $prefix . 'user',
78 | 'type' => 'user_ajax_search',
79 | 'query_args' => array(
80 | 'role' => array( 'Subscriber' ),
81 | 'search_columns' => array( 'user_login', 'user_email' )
82 | )
83 | ) );
84 |
85 | // Multiple users
86 | $cmb_demo->add_field( array(
87 | 'name' => __( 'Multiple users', 'cmb2' ),
88 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
89 | 'id' => $prefix . 'users',
90 | 'type' => 'user_ajax_search',
91 | 'multiple-item' => true,
92 | 'limit' => 5,
93 | 'query_args' => array(
94 | 'role__not_in' => array( 'Administrator', 'Editor' ),
95 | )
96 | ) );
97 |
98 | // Single term
99 | $cmb_demo->add_field( array(
100 | 'name' => __( 'Attached term', 'cmb2' ),
101 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
102 | 'id' => $prefix . 'term',
103 | 'type' => 'term_ajax_search',
104 | 'query_args' => array(
105 | 'taxonomy' => 'category',
106 | 'childless' => true
107 | )
108 | ) );
109 |
110 | // Multiple terms
111 | $cmb_demo->add_field( array(
112 | 'name' => __( 'Multiple terms', 'cmb2' ),
113 | 'desc' => __( 'Field description (optional)', 'cmb2' ),
114 | 'id' => $prefix . 'terms',
115 | 'type' => 'term_ajax_search',
116 | 'multiple-item' => true,
117 | 'limit' => -1,
118 | 'query_args' => array(
119 | 'taxonomy' => 'post_tag',
120 | 'hide_empty' => false
121 | )
122 | ) );
123 |
124 | }
125 | ```
126 |
127 | ## Customize results output
128 |
129 | You can use `cmb_{$field_id}_ajax_search_result_text` to customize the text returned from ajax searches and `cmb_{$field_id}_ajax_search_result_link` to customize the link, check next example:
130 |
131 | ```php
132 | add_filter( 'cmb_your_prefix_demo_posts_ajax_search_result_text', 'cmb2_ajax_search_custom_field_text', 10, 3 );
133 | function cmb2_ajax_search_custom_field_text( $text, $object_id, $object_type ) {
134 | $text = sprintf( '#%s - %s', $object_id, $text ); // #123 - Post title
135 |
136 | return $text;
137 | }
138 |
139 | add_filter( 'cmb_your_prefix_demo_posts_ajax_search_result_link', 'cmb2_ajax_search_custom_field_link', 10, 3 );
140 | function cmb2_ajax_search_custom_field_link( $link, $object_id, $object_type ) {
141 | if( $object_id == 123 ) {
142 | $link = '#';
143 | }
144 |
145 | return $link;
146 | }
147 | ```
148 |
149 | ## Retrieve the field value
150 |
151 | If multiple == false will return the ID of attached object:
152 | `get_post_meta( get_the_ID(), 'your_field_id', true );`
153 |
154 | If multiple == true will return an array of IDs of attached object:
155 | `get_post_meta( get_the_ID(), 'your_field_id', false );`
156 |
157 | ## Changelog
158 |
159 | ### 1.0.3
160 |
161 | * Fixed issues with repeatable fields
162 | * Removed unused code
163 | * Moved to new paramenter `multiple-item` to avoid conflicts with CMB2
164 |
165 | ### 1.0.2
166 | * Updated devbridgeAutocomplete lib
167 |
168 | ### 1.0.1
169 | * Group fields support
170 | * Widget area support
171 | * Use of devbridgeAutocomplete() instead of autocomplete() to avoid errors
172 |
173 | ### 1.0.0
174 | * Initial commit
175 |
--------------------------------------------------------------------------------