├── .gitignore ├── README.md ├── dynamic-fields-on-relationship ├── README.md ├── dynamic-fields-on-relationship.js ├── dynamic-fields-on-relationship.php └── group_57d9e88cbd4ed.json ├── dynamic-repeater-on-category ├── README.md ├── acf-json │ ├── group_57c9938e52fab.json │ ├── group_57c9948e8080e.json │ └── index.php ├── dynamic-repeater-on-category.js ├── dynamic-repeater-on-category.php └── extras.php ├── dynamic-select-example ├── README.md ├── cpt-ui-export.txt ├── dynamic-select-on-select.js ├── group_city_fields.json ├── group_post_fields.json ├── group_state_fields.json ├── my-acf-extension.js └── my-acf-extension.php ├── dynamic-text-based-on-user-select ├── README.md ├── dynamic-text-based-on-user-select.js ├── dynamic-text-based-on-user-select.php └── group_57ab9d41279f5.json ├── repeater-ajax-load-more ├── README.md ├── group_57cae2b099966.json ├── repeater-ajax-load-more-template-code.php └── repeater-ajax-load-more.php └── unique-repeater-checkbox ├── README.md ├── unique-repeater-checkbox-acf57.js ├── unique-repeater-checkbox.js └── unique-repeater-checkbox.php /.gitignore: -------------------------------------------------------------------------------- 1 | _notes 2 | config.codekit 3 | .DS_Store 4 | *.LCK 5 | *.svn 6 | __assets/* 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACf Dynamic AJAX Field Examples 2 | 3 | ## Important Note 4 | ACF In constantly changing. It is possible that any of the examples here may stop working with an update to ACF. I do not actively maintian these examples or continuously monitor them to make sure they are working. This repository was created to hold examples of some things that may help other. If you find that something is not working as expected you can open an issue, however, unless I have the free time the chances of me fixing the issue are small. This is why I will gladly take pull request for fixing problems with compatibility. 5 | 6 | ***MANY OF THESE EXAMPLES CURRENTLY DO NOT WORK WITH ACF >= 5.7.0 AND NEED TO BE REWORKED FOR THIS VERION. THE ONES THAT HAVE BEEN REWORKED HAVE NOTES ABOUT IT. THERE WAS A SIGNIFICANT CHANGE IN ACFs JS API IN VERSION 5.7.0*** 7 | 8 | ***These examples only works in ACF5*** 9 | 10 | This repo contains several examples of using AJAX to dynamically load fields based on the values 11 | in other fields. See the individual example folders for more information. 12 | 13 | There are also some other examples here that are more complicated than what can be found in my 14 | [ACF Filters & Functions Repo](https://github.com/Hube2/acf-filters-and-functions) becuase each 15 | of these examples requires code in several files. 16 | 17 | The following is a brief explanation of each example 18 | 19 | **dynamic-fields-on-relationship:** This example shows how to load additional fields from a post selected 20 | in a relationship field. 21 | 22 | **dynamic-repeater-on-category:** This example is a bit more complicated than the ones below. It will load a 23 | repeater field located on a post with values from a repeater field of the chosen post category. 24 | 25 | **dynamic-select-example:** *updated JS to work with ACF >= 5.7* Load values into a select field based on a choice made in another select field 26 | 27 | **dynamic-text-based-on-user-select:** Loads text fields based on the selection made for a user field 28 | 29 | **repeater-ajax-load-more:** This is an example of how to create a "Load More" feature for a repeater field. 30 | 31 | **unique-repeater-checkbox:** *updated JS to work with ACF >= 5.7* 32 | This example shows how to create a true/false field in a repeater that will only 33 | allow the field to be checked in one row of the repeater. A multi-row radio field. 34 | 35 | -------------------------------------------------------------------------------- /dynamic-fields-on-relationship/README.md: -------------------------------------------------------------------------------- 1 | #Dynamic Field on Relationship 2 | 3 | ***This example only works in ACF5*** 4 | 5 | In this example we are going to populate some fields based on a selection made in a relationship field. This is 6 | something that has come up several times on the ACF Support Forum. What people want to do is pupulate fields, like 7 | title, except and maybe an image from the related post and allow the editor to change these values for the 8 | relationship. 9 | 10 | Please note, that this example will only deal with a single relationship field that allows only a single choice for 11 | that field. It will not work on something like a repeater sub field to populate other values on the same row or 12 | anything more complicated. 13 | 14 | ## Setup 15 | 16 | In order to set up this example you will need to: 17 | 18 | 1. Create a relationship field 19 | 2. Create a title "text" field to be populated 20 | 3. Create an excerpt "textarea" field to be populated 21 | 4. Create and image field to be populated 22 | 5. Copy the field keys from the fields you've created into the JavaScript code example 23 | 24 | (If you'd like to test this with what I set up I have included the JSON field group in the files) 25 | 26 | ## Code Comments 27 | Look at the files and comments in them for information on what's going on and why. 28 | 29 | Please note, like all my examples in this repo, I use generic names for all files, functions and classes. 30 | These files, functions and classes should be renamed so that they do not conflict with other code. This is meant 31 | as an example only and is not meant to just be copied and pasted into your functions.php file except for the 32 | purpose of seeing this exact example in action. 33 | 34 | Some of the things that this example does that might be usefull in other projects include 35 | * How to get the selected value of a relationship field 36 | * How to do an AJAX request using ACF 37 | * How to populate other fields based on the results of an AJAX request 38 | * How to populate and show an ACF image field 39 | -------------------------------------------------------------------------------- /dynamic-fields-on-relationship/dynamic-fields-on-relationship.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(document).ready(function($){ 3 | // make sure acf is loaded, it should be, but just in case 4 | if (typeof acf == 'undefined') { return; } 5 | 6 | // extend the acf.ajax object 7 | // you should probably rename this var 8 | var myACFextension = acf.ajax.extend({ 9 | events: { 10 | // this data-key must match the field key for the relationship field that 11 | // will trigger the change of the other fields, in this case, the relationship field 12 | // is a Select2 field and we need to jump through a few extra hoops to set 13 | // an event and get the value. When ACF updates the value it triggers a change 14 | // action on a different hidden field than the one that will contain the value 15 | 'change [data-key="field_57d9e8a359b75"] .acf-input input': '_relationship_change', 16 | }, 17 | 18 | _relationship_change: function(e) { 19 | 20 | // clear existing values from the fields we will update 21 | 22 | // clear input field 23 | // data-key == field key of title field 24 | $('[data-key="field_57d9e8d659b76"] input').val(''); 25 | 26 | // clear textarea field 27 | // date-key == field key of excerpt field 28 | $('[data-key="field_57d9e8f559b77"] textarea').val(''); 29 | 30 | // clear image field 31 | // data-key == field key of image field 32 | // target the link that will remove the image and trigger a click 33 | $('[data-key="field_57d9e92159b78"] a[data-name="remove"]').trigger('click'); 34 | 35 | // maybe I'm slow, but it took several hours to figure out how to get 36 | // the actual selected value of a relationship field 37 | // this first line gets a list of the hidden input elements 38 | // for our example there should be only one element 39 | var list = e.$el.closest('.acf-field').find('.values .list input[type="hidden"]'); 40 | // set the default value 41 | var $value = 0; 42 | if (list.length) { 43 | // if the list lenght > 0 44 | // in our case a lenght of 1 45 | // get the value of the first element 46 | $value = list[0].value; 47 | } 48 | 49 | // if there is no value, exit 50 | if (!$value) { 51 | return; 52 | } 53 | 54 | 55 | // now we can do our ajax request 56 | 57 | // I assume this tests to see if there is already a request 58 | // for this and cancels it if there is 59 | if( this.request) { 60 | this.request.abort(); 61 | } 62 | 63 | // I don't know exactly what it does 64 | // acf does it so I copied it 65 | var self = this, 66 | data = this.o; 67 | 68 | // set the ajax action that's set up in php 69 | data.action = 'load_relationship_content'; 70 | // set the term id value to be submitted 71 | data.related_post = $value; 72 | 73 | // this gets the post ID that we set when localizing the script 74 | // you should change this object name to something unique 75 | // will will also need to change this object name in the PHO file 76 | // where this script is enqueued 77 | data.post_id = my_acf_extension_object.post_id 78 | 79 | // we need to get information about the image field 80 | // to send that along with the request 81 | data.image_size = $('[data-key="field_57d9e92159b78"]').find('.acf-image-uploader').data('preview_size'); 82 | 83 | 84 | // this is another bit I'm not sure about 85 | // copied from ACF 86 | data.exists = []; 87 | 88 | // this the request is copied from ACF 89 | this.request = $.ajax({ 90 | url: acf.get('ajaxurl'), 91 | data: acf.prepare_for_ajax(data), 92 | type: 'post', 93 | dataType: 'json', 94 | async: true, 95 | success: function(json){ 96 | 97 | if (!json) { 98 | return; 99 | } 100 | 101 | // put the values into the fields 102 | if (json['title']) { 103 | // data-key == field key of title field 104 | $('[data-key="field_57d9e8d659b76"] input').val(json['title']); 105 | } 106 | if (json['excerpt']) { 107 | // date-key == field key of excerpt field 108 | $('[data-key="field_57d9e8f559b77"] textarea').val(json['excerpt']); 109 | } 110 | if (json['image']) { 111 | // data-key == field key of image field 112 | // put the id value into the hidden field 113 | $('[data-key="field_57d9e92159b78"] input[type="hidden"]').val(json['image']['id']); 114 | // put the url into the img element 115 | $('[data-key="field_57d9e92159b78"] img').attr('src', json['image']['url']); 116 | // set the image field to show 117 | $('[data-key="field_57d9e92159b78"]').find('.acf-image-uploader').addClass('has-value'); 118 | } 119 | }, 120 | error: function(jqXHR, textStatus, error) { 121 | alert (jqXHR+' : '+textStatus+' : '+error); 122 | } 123 | }); 124 | }, 125 | }); 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /dynamic-fields-on-relationship/dynamic-fields-on-relationship.php: -------------------------------------------------------------------------------- 1 | $image_id, 62 | 'url' => $image_details[0] 63 | ); 64 | } 65 | } 66 | 67 | // put all the values into an array and return it as json 68 | $array = array( 69 | 'title' => get_field('relationship_title', $post_id), 70 | 'excerpt' => get_field('relationship_excerpt', $post_id), 71 | 'image' => $image 72 | ); 73 | echo json_encode($array); 74 | exit; 75 | } 76 | 77 | // this is a different related post 78 | // get the post and set it up 79 | global $post; 80 | $post = get_post($related_post); 81 | setup_postdata($post); 82 | 83 | // get the excerpt 84 | $excerpt = trim($post->post_excerpt); 85 | if (!$excerpt) { 86 | // the excerpt is not set, create on from the content 87 | $excerpt = apply_filters('the_excerpt', $post->post_content); 88 | } 89 | 90 | // get the image if any 91 | $image = false; 92 | $image_id = intval(get_post_thumbnail_id($post->ID)); 93 | if ($image_id) { 94 | $image_details = wp_get_attachment_image_src($image_id, $image_size); 95 | if ($image_details) { 96 | $image = array( 97 | 'id' => $image_id, 98 | 'url' => $image_details[0] 99 | ); 100 | } 101 | } 102 | 103 | // put all the values into an array and return it as json 104 | $array = array( 105 | 'title' => $post->post_title, 106 | 'excerpt' => $excerpt, 107 | 'image' => $image 108 | ); 109 | echo json_encode($array); 110 | exit; 111 | 112 | } // end public function load_content_from_relationship 113 | 114 | public function enqueue_script() { 115 | // enqueue acf extenstion 116 | 117 | // only enqueue the script on the post page where it needs to run 118 | /* *** THIS IS IMPORTANT 119 | ACF uses the same scripts as well as the same field identification 120 | markup (the data-key attribute) if the ACF field group editor 121 | because of this, if you load and run your custom javascript on 122 | the field group editor page it can have unintended side effects 123 | on this page. It is important to alway make sure you're only 124 | loading scripts where you need them. 125 | */ 126 | global $post; 127 | if (!$post || 128 | !isset($post->ID) || 129 | get_post_type($post->ID) != 'post') { 130 | return; 131 | } 132 | 133 | // the handle should be changed to your own unique handle 134 | $handle = 'my-dynamic-repeater-on-relationship'; 135 | 136 | // I'm using this method to set the src because 137 | // I don't know where this file will be located 138 | // you should alter this to use the correct functions 139 | // to get the theme, template or plugin path 140 | // to set the src value to point to the javascript file 141 | $src = '/'.str_replace(ABSPATH, '', dirname(__FILE__)).'/dynamic-fields-on-relationship.js'; 142 | // make this script dependent on acf-input 143 | $depends = array('acf-input'); 144 | 145 | wp_register_script($handle, $src, $depends); 146 | 147 | // localize the script with the current post id 148 | // we will need the current post ID to get existing 149 | // values from the post 150 | 151 | // you should change this object name to something unique 152 | // will will also need to change this object name in the JS file 153 | $object = 'my_acf_extension_object'; 154 | 155 | $data = array('post_id' => $post->ID); 156 | wp_localize_script($handle, $object, $data); 157 | 158 | wp_enqueue_script($handle); 159 | } // end public function enqueue_script 160 | 161 | } // end class my_dynmamic_field_on_relationship 162 | 163 | ?> 164 | -------------------------------------------------------------------------------- /dynamic-fields-on-relationship/group_57d9e88cbd4ed.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57d9e88cbd4ed", 3 | "title": "Dynamic Field on Relationship Example", 4 | "fields": [ 5 | { 6 | "key": "field_57d9e8a359b75", 7 | "label": "relationship", 8 | "name": "relationship", 9 | "type": "relationship", 10 | "instructions": "This is the relationship field. When you select a related post in this field the title, excerpt and featured image from that post will be inserted into the fields below.", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "post_type": [ 19 | "post" 20 | ], 21 | "taxonomy": [], 22 | "filters": [ 23 | "search", 24 | "post_type", 25 | "taxonomy" 26 | ], 27 | "elements": "", 28 | "min": "", 29 | "max": 1, 30 | "return_format": "object" 31 | }, 32 | { 33 | "key": "field_57d9e8d659b76", 34 | "label": "relationship title", 35 | "name": "relationship_title", 36 | "type": "text", 37 | "instructions": "The title of the related post will be put here", 38 | "required": 0, 39 | "conditional_logic": 0, 40 | "wrapper": { 41 | "width": "", 42 | "class": "", 43 | "id": "" 44 | }, 45 | "default_value": "", 46 | "placeholder": "", 47 | "prepend": "", 48 | "append": "", 49 | "maxlength": "" 50 | }, 51 | { 52 | "key": "field_57d9e8f559b77", 53 | "label": "relationship excerpt", 54 | "name": "relationship_excerpt", 55 | "type": "textarea", 56 | "instructions": "The excerpt from the related post will be put there", 57 | "required": 0, 58 | "conditional_logic": 0, 59 | "wrapper": { 60 | "width": "", 61 | "class": "", 62 | "id": "" 63 | }, 64 | "default_value": "", 65 | "placeholder": "", 66 | "maxlength": "", 67 | "rows": "", 68 | "new_lines": "wpautop" 69 | }, 70 | { 71 | "key": "field_57d9e92159b78", 72 | "label": "relationship image", 73 | "name": "relationship_image", 74 | "type": "image", 75 | "instructions": "The featured image from the related post will go here", 76 | "required": 0, 77 | "conditional_logic": 0, 78 | "wrapper": { 79 | "width": "", 80 | "class": "", 81 | "id": "" 82 | }, 83 | "return_format": "array", 84 | "preview_size": "thumbnail", 85 | "library": "all", 86 | "min_width": "", 87 | "min_height": "", 88 | "min_size": "", 89 | "max_width": "", 90 | "max_height": "", 91 | "max_size": "", 92 | "mime_types": "" 93 | } 94 | ], 95 | "location": [ 96 | [ 97 | { 98 | "param": "post_type", 99 | "operator": "==", 100 | "value": "post" 101 | } 102 | ] 103 | ], 104 | "menu_order": 0, 105 | "position": "normal", 106 | "style": "default", 107 | "label_placement": "top", 108 | "instruction_placement": "label", 109 | "hide_on_screen": "", 110 | "active": 1, 111 | "description": "", 112 | "modified": 1473956949 113 | } -------------------------------------------------------------------------------- /dynamic-repeater-on-category/README.md: -------------------------------------------------------------------------------- 1 | #Dynamic Repeater Base on Term 2 | 3 | ***This example only works in ACF5*** 4 | 5 | In this example we will create a repeater field on a Post that is dynamically populated based on 6 | the selection of a term in a taxonomy. The content of the repeater will be taken from a repeater 7 | that attached to the term. 8 | 9 | The repeater on the term allows you to add a list of "Features". On the post these "Features" are 10 | dynamically added and require adding a "Value" for each "Feature" 11 | 12 | See the comments in the file "extras.php" for more information on how the post type and taxonomy 13 | are set up. Please make sure you read the important note in this file about removing the standard 14 | WP taxonomy meta box when registering a taxonomy. 15 | 16 | See comments in other files to see how it works. 17 | 18 | Please not, like all my examples in this repo, I use generic names for all files, functions and classes. 19 | These files, functions and classes should be renamed so that they do not conflict with other code. 20 | There are specific things that must be edited to match your use, for example, field keys and such. 21 | 22 | If you want to try out this example you will need to create some terms in the taxonomy and populate 23 | the "Features" repeater with some content. 24 | 25 | This example has a lot of things in it that people might find useful. 26 | * How to dynamically delete all the rows of a repeater 27 | * How to dynamically add rows to a repeater 28 | * How to dynamically populate the sub fields on each row of a repeater 29 | * and of course, how to do an AJAX request based on a taxonomy field -------------------------------------------------------------------------------- /dynamic-repeater-on-category/acf-json/group_57c9938e52fab.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57c9938e52fab", 3 | "title": "Term Repeater for Example", 4 | "fields": [ 5 | { 6 | "key": "field_57c99408fefa1", 7 | "label": "Features", 8 | "name": "features", 9 | "type": "repeater", 10 | "instructions": "", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "collapsed": "", 19 | "min": "", 20 | "max": "", 21 | "layout": "table", 22 | "button_label": "Add Row", 23 | "sub_fields": [ 24 | { 25 | "key": "field_57c99428fefa2", 26 | "label": "Feature", 27 | "name": "feature", 28 | "type": "text", 29 | "instructions": "", 30 | "required": 0, 31 | "conditional_logic": 0, 32 | "wrapper": { 33 | "width": "", 34 | "class": "", 35 | "id": "" 36 | }, 37 | "default_value": "", 38 | "placeholder": "", 39 | "prepend": "", 40 | "append": "", 41 | "maxlength": "" 42 | } 43 | ] 44 | } 45 | ], 46 | "location": [ 47 | [ 48 | { 49 | "param": "taxonomy", 50 | "operator": "==", 51 | "value": "sprocket-category" 52 | } 53 | ] 54 | ], 55 | "menu_order": 0, 56 | "position": "normal", 57 | "style": "default", 58 | "label_placement": "top", 59 | "instruction_placement": "label", 60 | "hide_on_screen": "", 61 | "active": 1, 62 | "description": "", 63 | "modified": 1472828549 64 | } -------------------------------------------------------------------------------- /dynamic-repeater-on-category/acf-json/group_57c9948e8080e.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57c9948e8080e", 3 | "title": "Features for Example", 4 | "fields": [ 5 | { 6 | "key": "field_57c994a6d8ee5", 7 | "label": "Category", 8 | "name": "category", 9 | "type": "taxonomy", 10 | "instructions": "", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "taxonomy": "sprocket-category", 19 | "field_type": "select", 20 | "allow_null": 0, 21 | "add_term": 1, 22 | "save_terms": 1, 23 | "load_terms": 1, 24 | "return_format": "id", 25 | "multiple": 0 26 | }, 27 | { 28 | "key": "field_57c994f2d8ee6", 29 | "label": "Features", 30 | "name": "features", 31 | "type": "repeater", 32 | "instructions": "", 33 | "required": 0, 34 | "conditional_logic": 0, 35 | "wrapper": { 36 | "width": "", 37 | "class": "", 38 | "id": "" 39 | }, 40 | "collapsed": "", 41 | "min": "", 42 | "max": "", 43 | "layout": "table", 44 | "button_label": "Add Row", 45 | "sub_fields": [ 46 | { 47 | "key": "field_57c99507d8ee7", 48 | "label": "Feature", 49 | "name": "feature", 50 | "type": "text", 51 | "instructions": "", 52 | "required": 0, 53 | "conditional_logic": 0, 54 | "wrapper": { 55 | "width": "", 56 | "class": "", 57 | "id": "" 58 | }, 59 | "default_value": "", 60 | "placeholder": "", 61 | "prepend": "", 62 | "append": "", 63 | "maxlength": "" 64 | }, 65 | { 66 | "key": "field_57c99515d8ee8", 67 | "label": "Value", 68 | "name": "value", 69 | "type": "text", 70 | "instructions": "", 71 | "required": 1, 72 | "conditional_logic": 0, 73 | "wrapper": { 74 | "width": "", 75 | "class": "", 76 | "id": "" 77 | }, 78 | "default_value": "", 79 | "placeholder": "", 80 | "prepend": "", 81 | "append": "", 82 | "maxlength": "" 83 | } 84 | ] 85 | } 86 | ], 87 | "location": [ 88 | [ 89 | { 90 | "param": "post_type", 91 | "operator": "==", 92 | "value": "sprocket" 93 | } 94 | ] 95 | ], 96 | "menu_order": 0, 97 | "position": "normal", 98 | "style": "default", 99 | "label_placement": "top", 100 | "instruction_placement": "label", 101 | "hide_on_screen": "", 102 | "active": 1, 103 | "description": "", 104 | "modified": 1472828708 105 | } -------------------------------------------------------------------------------- /dynamic-repeater-on-category/acf-json/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Untitled Document 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /dynamic-repeater-on-category/dynamic-repeater-on-category.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(document).ready(function($){ 3 | // make sure acf is loaded, it should be, but just in case 4 | if (typeof acf == 'undefined') { return; } 5 | 6 | // extend the acf.ajax object 7 | // you should probably rename this var 8 | var myACFextension = acf.ajax.extend({ 9 | events: { 10 | // this data-key must match the field key taxonomy field 11 | // we want to trigger the change 12 | // in this case, the taxonomy field is a Select2 field 13 | // and we want the value of a hidden field and not the select field 14 | 'change [data-key="field_57c994a6d8ee5"] input[type="hidden"]': '_change_term', 15 | // this entry is to cause the field to update on page load 16 | 'ready [data-key="field_57c994a6d8ee5"] input[type="hidden"]': '_change_term', 17 | 18 | // for this example we also want to hide all of the "add row" buttons 19 | // because we only want the user to be able to set existing values 20 | // and not add new ones so we'll add another ready function 21 | // that will do this maintenance, this is our own special action 22 | // setup is not a real action, we're creating a new one 23 | 'setup [data-key="field_57c994a6d8ee5"] input[type="hidden"]': '_setup', 24 | }, 25 | 26 | // this is our function that will perform the 27 | // ajax request when the state value is changed 28 | _change_term: function(e){ 29 | 30 | // get the value of the taxonomy field 31 | $value = e.$el.val(); 32 | 33 | // remove any existing rows of the repeater, except the clone row 34 | // by triggering each row's remove-row click event 35 | // the data-key is the field key of the repeater 36 | $('div[data-key="field_57c994f2d8ee6"] tr a[data-event="remove-row"]').not('div[data-key="field_57c994f2d8ee6"] tr.acf-clone a[data-event="remove-row"]').trigger('click'); 37 | 38 | 39 | 40 | // I assume this tests to see if there is already a request 41 | // for this and cancels it if there is 42 | if( this.request) { 43 | this.request.abort(); 44 | } 45 | 46 | // I don't know exactly what it does 47 | // acf does it so I copied it 48 | var self = this, 49 | data = this.o; 50 | 51 | // set the ajax action that's set up in php 52 | data.action = 'load_features_from_term'; 53 | // set the term id value to be submitted 54 | data.term_id = $value; 55 | 56 | // this gets the post ID that we set when localizing the script 57 | // you should change this object name to something unique 58 | // will will also need to change this object name in the PHO file 59 | // where this script is enqueued 60 | data.post_id = my_acf_extension_object.post_id 61 | 62 | // this is another bit I'm not sure about 63 | // copied from ACF 64 | data.exists = []; 65 | 66 | // this the request is copied from ACF 67 | this.request = $.ajax({ 68 | url: acf.get('ajaxurl'), 69 | data: acf.prepare_for_ajax(data), 70 | type: 'post', 71 | dataType: 'json', 72 | async: true, 73 | success: function(json){ 74 | 75 | if (!json) { 76 | return; 77 | } 78 | // add enough rows to the repeater to hold the values 79 | var len = json.length; 80 | for (i=0; i=0; i-=1) { 100 | // get the ID of this item 101 | var id = feature_list[i].getAttribute('data-id'); 102 | // populate the featur 103 | $('div[data-key="field_57c994f2d8ee6"] tr[data-id="'+id+'"] td[data-key="field_57c99507d8ee7"] input').val(json[json_item]['field_57c99507d8ee7']); 104 | // populate the value 105 | $('div[data-key="field_57c994f2d8ee6"] tr[data-id="'+id+'"] td[data-key="field_57c99515d8ee8"] input').val(json[json_item]['field_57c99515d8ee8']); 106 | // decrease json item 107 | json_item-=1; 108 | } 109 | // last thing to do is trigger setup to make sure we keep the add/remvove buttons hidden 110 | $('[data-key="field_57c994a6d8ee5"] input').trigger('setup'); 111 | } 112 | }); 113 | }, // end _change_term 114 | 115 | // this function will hide the add/remove rows buttons 116 | _setup: function(e) { 117 | // hide all add/delete row links 118 | // this data key is the key for the repeater field 119 | $('div[data-key="field_57c994f2d8ee6"] a[data-event="remove-row"]').css('display', 'none'); 120 | $('div[data-key="field_57c994f2d8ee6"] a[data-event="add-row"]').css('display', 'none'); 121 | }, // end _setup 122 | }); 123 | 124 | // triger the setup action on page load 125 | $('[data-key="field_57c994a6d8ee5"] input').trigger('setup'); 126 | }); -------------------------------------------------------------------------------- /dynamic-repeater-on-category/dynamic-repeater-on-category.php: -------------------------------------------------------------------------------- 1 | get_sub_field('feature'), 76 | 'field_57c99515d8ee8' => get_sub_field('value') 77 | ); 78 | } // end while have rows 79 | } // end if have rows 80 | } // end if is current term 81 | //print_r($values); exit; 82 | if (count($values)) { 83 | // values from post, return them 84 | echo json_encode($values); 85 | exit; 86 | } 87 | // no existing value 88 | // get features from term 89 | if (have_rows('features', $taxonomy.'_'.$term_id)) { 90 | while (have_rows('features', $taxonomy.'_'.$term_id)) { 91 | the_row(); 92 | $values[] = array( 93 | // key value pairs for fields to be updated 94 | // by callback function 95 | 'field_57c99507d8ee7' => get_sub_field('feature'), 96 | 'field_57c99515d8ee8' => '' 97 | ); 98 | } // end while have rows 99 | } // end if have rows 100 | 101 | echo json_encode($values); 102 | exit; 103 | 104 | } // end public function load_features_from_term 105 | 106 | public function enqueue_script() { 107 | // enqueue acf extenstion 108 | 109 | // only enqueue the script on the post page where it needs to run 110 | /* *** THIS IS IMPORTANT 111 | ACF uses the same scripts as well as the same field identification 112 | markup (the data-key attribute) if the ACF field group editor 113 | because of this, if you load and run your custom javascript on 114 | the field group editor page it can have unintended side effects 115 | on this page. It is important to alway make sure you're only 116 | loading scripts where you need them. 117 | */ 118 | global $post; 119 | if (!$post || 120 | !isset($post->ID) || 121 | get_post_type($post->ID) != 'sprocket') { 122 | return; 123 | } 124 | 125 | // the handle should be changed to your own unique handle 126 | $handle = 'my-acf-extension'; 127 | 128 | // I'm using this method to set the src because 129 | // I don't know where this file will be located 130 | // you should alter this to use the correct fundtions 131 | // to set the src value to point to the javascript file 132 | $src = '/'.str_replace(ABSPATH, '', dirname(__FILE__)).'/dynamic-repeater-on-category.js'; 133 | // make this script dependent on acf-input 134 | $depends = array('acf-input'); 135 | 136 | wp_register_script($handle, $src, $depends); 137 | 138 | // localize the script with the current post id 139 | // we will need the current post ID to get existing 140 | // values from the post 141 | 142 | // you should change this object name to something unique 143 | // will will also need to change this object name in the JS file 144 | $object = 'my_acf_extension_object'; 145 | 146 | $data = array('post_id' => $post->ID); 147 | wp_localize_script($handle, $object, $data); 148 | 149 | wp_enqueue_script($handle); 150 | } // end public function enqueue_script 151 | 152 | } // end class my_dynamic_acf_extension 153 | 154 | ?> -------------------------------------------------------------------------------- /dynamic-repeater-on-category/extras.php: -------------------------------------------------------------------------------- 1 | 'Sprockets', 52 | 'singular_name' => 'Sproket', 53 | ); 54 | $taxonomies = array( 55 | // this is the taxonomy we'll set up below 56 | 'sprocket-category' 57 | ); 58 | $args = array( 59 | 'label' => 'Sprockets', 60 | 'labels' => $labels, 61 | 'description' => '', 62 | 'public' => true, 63 | 'show_ui' => true, 64 | 'show_in_rest' => false, 65 | 'rest_base' => '', 66 | 'has_archive' => true, 67 | 'show_in_menu' => true, 68 | 'exclude_from_search' => false, 69 | 'capability_type' => 'post', 70 | 'map_meta_cap' => true, 71 | 'hierarchical' => false, 72 | 'rewrite' => array( 73 | 'slug' => 'blunt-product', 74 | 'with_front' => true 75 | ), 76 | 'query_var' => true, 77 | 'supports' => array( 78 | 'title', 79 | 'editor', 80 | 'thumbnail', 81 | 'excerpt', 82 | 'custom-fields', 83 | 'comments', 84 | 'revisions', 85 | 'author', 86 | 'page-attributes', 87 | 'post-formats' 88 | ), 89 | 'taxonomies' => $taxonomies, 90 | ); 91 | register_post_type($post_type, $args ); 92 | 93 | 94 | $labels = array( 95 | 'name' => 'Sprocket Categories', 96 | 'singular_name' => 'Sprocket Category', 97 | ); 98 | $args = array( 99 | 'label' => 'Sprocket Categories', 100 | 'labels' => $labels, 101 | 'public' => true, 102 | 'hierarchical' => true, 103 | 'label' => 'Sprocket Categories', 104 | 'show_ui' => true, 105 | 'query_var' => true, 106 | 'rewrite' => array( 107 | 'slug' => 'sprocket-category', 108 | 'with_front' => true 109 | ), 110 | 'show_admin_column' => true, 111 | 'show_in_rest' => false, 112 | 'rest_base' => '', 113 | 'show_in_quick_edit' => true, 114 | 115 | /* 116 | *** IMPORTANT NOTE *** 117 | setting meta_box_cb to an empty string 118 | causes WP to not show the standard meta box 119 | for selecting the terms of a taxonomy 120 | this is important for this example 121 | because the term must be selected using 122 | the ACF field and not the meta box 123 | if the term is selected in the meta box 124 | none of the code in this example will work 125 | */ 126 | 'meta_box_cb' => '' 127 | ); 128 | register_taxonomy('sprocket-category', array($post_type), $args); 129 | 130 | } // end public function register 131 | 132 | } // end class my_dynamic_acf_extension_extras 133 | 134 | ?> -------------------------------------------------------------------------------- /dynamic-select-example/README.md: -------------------------------------------------------------------------------- 1 | # ACf Dynamic AJAX Select Example 2 | 3 | ***This example only works in ACF5*** 4 | 5 | This repo is an example of how to dynamically load an ACF select field based on the selection made in 6 | another ACF select field. This example can be used as the basis of understanding how to extend ACF to 7 | use AJAX for the creation of dyanmic fields. Every field type in ACF will need slightly different code 8 | to get this to work. This is presented in the hope that it will help others to better understand how 9 | to extend the functionality of ACF. 10 | 11 | See the comments in the files for comments about the code and what it is doing. Please note that the 12 | PHP in this example uses OOP rather than simple functions. 13 | 14 | In this example I have set up two custom post types. The first custom post type is named 'State' and it 15 | is used to populate a list of US States. The second custom post type is named 'City' and it is used to 16 | populate a list of cities that are located in each state. The City post type includes a select 17 | field to select a stated from the list of states. 18 | 19 | The file cpt-ui-export.txt is an export from the plugin 20 | [Custom Post Type UI](https://github.com/WebDevStudios/custom-post-type-ui) which is what I generally 21 | use to set up custom post types. It can be used if you want to test out this example. 22 | 23 | The JSON files are exports from ACF of the three field groups I used for this excercise. You can import these 24 | if you want to test this example. 25 | 26 | If you want to test this you'll need to add some states and cities yourself 27 | 28 | The goal is then to add two select fields to the "Post" post type. A user will select a state in the state 29 | select field and this will cause the cities in that state to by dynamically populated with its related cities. 30 | 31 | For the rest of the explanation see the comments in the PHP and JS files 32 | -------------------------------------------------------------------------------- /dynamic-select-example/cpt-ui-export.txt: -------------------------------------------------------------------------------- 1 | {"state":{"name":"state","label":"States","singular_label":"State","description":"","public":"true","show_ui":"true","show_in_nav_menus":"true","show_in_rest":"false","rest_base":"","has_archive":"true","has_archive_string":"","exclude_from_search":"false","capability_type":"post","hierarchical":"false","rewrite":"true","rewrite_slug":"","rewrite_withfront":"true","query_var":"true","query_var_slug":"","menu_position":"","show_in_menu":"true","show_in_menu_string":"","menu_icon":"","supports":["title","editor","thumbnail","excerpt","custom-fields","revisions"],"taxonomies":[],"labels":{"menu_name":"","all_items":"","add_new":"","add_new_item":"","edit_item":"","new_item":"","view_item":"","search_items":"","not_found":"","not_found_in_trash":"","parent":"","featured_image":"","set_featured_image":"","remove_featured_image":"","use_featured_image":"","archives":"","insert_into_item":"","uploaded_to_this_item":"","filter_items_list":"","items_list_navigation":"","items_list":""},"custom_supports":""},"city":{"name":"city","label":"Cities","singular_label":"City","description":"","public":"true","show_ui":"true","show_in_nav_menus":"true","show_in_rest":"false","rest_base":"","has_archive":"true","has_archive_string":"","exclude_from_search":"false","capability_type":"post","hierarchical":"false","rewrite":"true","rewrite_slug":"","rewrite_withfront":"true","query_var":"true","query_var_slug":"","menu_position":"","show_in_menu":"true","show_in_menu_string":"","menu_icon":"","supports":["title","editor","thumbnail","excerpt","custom-fields","revisions","page-attributes"],"taxonomies":[],"labels":{"menu_name":"","all_items":"","add_new":"","add_new_item":"","edit_item":"","new_item":"","view_item":"","search_items":"","not_found":"","not_found_in_trash":"","parent":"","featured_image":"","set_featured_image":"","remove_featured_image":"","use_featured_image":"","archives":"","insert_into_item":"","uploaded_to_this_item":"","filter_items_list":"","items_list_navigation":"","items_list":""},"custom_supports":""}} -------------------------------------------------------------------------------- /dynamic-select-example/dynamic-select-on-select.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | updated JS file for use with ACF >= 5.7.0 4 | */ 5 | 6 | 7 | jQuery(document).ready(function($){ 8 | if (typeof acf == 'undefined') { return; } 9 | 10 | /* 11 | In ACF >= 5.7.0 the acf.ajax object no longer exists so we can't extend it 12 | Instead we need to attach out event the old fashioned way 13 | and we need to do it using $(document).on because the element may 14 | be added dynamically and this is the only way to add events 15 | */ 16 | 17 | $(document).on('change', '[data-key="field_579376f522130"] .acf-input select', function(e) { 18 | // we are not going to do anyting in this anonymous function 19 | // the reason is explained below 20 | // call function, we need to pass the event and jQuery object 21 | update_cities_on_state_change(e, $); 22 | }); 23 | $('[data-key="field_579376f522130"] .acf-input select').trigger('ready'); 24 | }); 25 | 26 | // the actual function is separate from the above change function 27 | // the reason for this is that "this" has no real meaning in an anonymous 28 | // function as each call is a new JS object and we need "this" in order 29 | // to be to abort previous AJAX requests 30 | function update_cities_on_state_change(e, $) { 31 | if (this.request) { 32 | // if a recent request has been made abort it 33 | this.request.abort(); 34 | } 35 | 36 | // get the city select field, and remove all exisiting choices 37 | var city_select = $('[data-key="field_5793770922131"] select'); 38 | city_select.empty(); 39 | 40 | // get the target of the event and then get the value of that field 41 | var target = $(e.target); 42 | var state = target.val(); 43 | 44 | if (!state) { 45 | // no state selected 46 | // don't need to do anything else 47 | return; 48 | } 49 | 50 | // set and prepare data for ajax 51 | var data = { 52 | action: 'load_city_field_choices', 53 | state: state 54 | } 55 | 56 | // call the acf function that will fill in other values 57 | // like post_id and the acf nonce 58 | data = acf.prepareForAjax(data); 59 | 60 | // make ajax request 61 | // instead of going through the acf.ajax object to make requests like in <5.7 62 | // we need to do a lot of the work ourselves, but other than the method that's called 63 | // this has not changed much 64 | this.request = $.ajax({ 65 | url: acf.get('ajaxurl'), // acf stored value 66 | data: data, 67 | type: 'post', 68 | dataType: 'json', 69 | success: function(json) { 70 | if (!json) { 71 | return; 72 | } 73 | // add the new options to the city field 74 | for(i=0; i'+json[i]['label']+''; 76 | city_select.append(city_item); 77 | } 78 | } 79 | }); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /dynamic-select-example/group_city_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57937686d8c23", 3 | "title": "City Fields", 4 | "fields": [ 5 | { 6 | "key": "field_579376941cecc", 7 | "label": "State", 8 | "name": "state", 9 | "type": "select", 10 | "instructions": "", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "choices": { 19 | "this will be dynamically generated": "this will be dynamically generated" 20 | }, 21 | "default_value": [], 22 | "allow_null": 0, 23 | "multiple": 0, 24 | "ui": 0, 25 | "ajax": 0, 26 | "placeholder": "", 27 | "disabled": 0, 28 | "readonly": 0 29 | } 30 | ], 31 | "location": [ 32 | [ 33 | { 34 | "param": "post_type", 35 | "operator": "==", 36 | "value": "city" 37 | } 38 | ] 39 | ], 40 | "menu_order": 0, 41 | "position": "normal", 42 | "style": "default", 43 | "label_placement": "top", 44 | "instruction_placement": "label", 45 | "hide_on_screen": "", 46 | "active": 1, 47 | "description": "", 48 | "modified": 1469282192 49 | } -------------------------------------------------------------------------------- /dynamic-select-example/group_post_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_579376df468e7", 3 | "title": "Post Fields for City\/State", 4 | "fields": [ 5 | { 6 | "key": "field_579376f522130", 7 | "label": "State", 8 | "name": "state", 9 | "type": "select", 10 | "instructions": "", 11 | "required": 1, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "choices": { 19 | "this will be dynamically generated": "this will be dynamically generated" 20 | }, 21 | "default_value": [], 22 | "allow_null": 0, 23 | "multiple": 0, 24 | "ui": 0, 25 | "ajax": 0, 26 | "placeholder": "", 27 | "disabled": 0, 28 | "readonly": 0 29 | }, 30 | { 31 | "key": "field_5793770922131", 32 | "label": "City", 33 | "name": "city", 34 | "type": "select", 35 | "instructions": "", 36 | "required": 1, 37 | "conditional_logic": 0, 38 | "wrapper": { 39 | "width": "", 40 | "class": "", 41 | "id": "" 42 | }, 43 | "choices": { 44 | "this will be dynamically generated": "this will be dynamically generated" 45 | }, 46 | "default_value": [], 47 | "allow_null": 0, 48 | "multiple": 0, 49 | "ui": 0, 50 | "ajax": 0, 51 | "placeholder": "", 52 | "disabled": 0, 53 | "readonly": 0 54 | } 55 | ], 56 | "location": [ 57 | [ 58 | { 59 | "param": "post_type", 60 | "operator": "==", 61 | "value": "post" 62 | } 63 | ] 64 | ], 65 | "menu_order": 0, 66 | "position": "normal", 67 | "style": "default", 68 | "label_placement": "top", 69 | "instruction_placement": "label", 70 | "hide_on_screen": "", 71 | "active": 1, 72 | "description": "", 73 | "modified": 1469282196 74 | } -------------------------------------------------------------------------------- /dynamic-select-example/group_state_fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57937657a201f", 3 | "title": "State Fields", 4 | "fields": [ 5 | { 6 | "key": "field_5793766ab8167", 7 | "label": "Sate Abbreiviation", 8 | "name": "sate_abbreiviation", 9 | "type": "text", 10 | "instructions": "", 11 | "required": 1, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "default_value": "", 19 | "placeholder": "", 20 | "prepend": "", 21 | "append": "", 22 | "maxlength": "", 23 | "readonly": 0, 24 | "disabled": 0 25 | } 26 | ], 27 | "location": [ 28 | [ 29 | { 30 | "param": "post_type", 31 | "operator": "==", 32 | "value": "state" 33 | } 34 | ] 35 | ], 36 | "menu_order": 0, 37 | "position": "normal", 38 | "style": "default", 39 | "label_placement": "top", 40 | "instruction_placement": "label", 41 | "hide_on_screen": "", 42 | "active": 1, 43 | "description": "", 44 | "modified": 1469282194 45 | } -------------------------------------------------------------------------------- /dynamic-select-example/my-acf-extension.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(document).ready(function($){ 3 | // make sure acf is loaded, it should be, but just in case 4 | if (typeof acf == 'undefined') { return; } 5 | 6 | // extend the acf.ajax object 7 | // you should probably rename this var 8 | var myACFextension = acf.ajax.extend({ 9 | events: { 10 | // this data-key must match the field key for the state field on the post page where 11 | // you want to dynamically load the cities when the state is changed 12 | 'change [data-key="field_579376f522130"] select': '_state_change', 13 | // this entry is to cause the city field to be updated when the page is loaded 14 | 'ready [data-key="field_579376f522130"] select': '_state_change', 15 | }, 16 | 17 | // this is our function that will perform the 18 | // ajax request when the state value is changed 19 | _state_change: function(e){ 20 | 21 | // clear the city field options 22 | // the data-key is the field key of the city field on post 23 | var $select = $('[data-key="field_5793770922131"] select'); 24 | $select.empty(); 25 | 26 | // get the state selection 27 | var $value = e.$el.val(); 28 | 29 | // a lot of the following code is copied directly 30 | // from ACF and modified for our purpose 31 | 32 | // I assume this tests to see if there is already a request 33 | // for this and cancels it if there is 34 | if( this.state_request) { 35 | this.state_request.abort(); 36 | } 37 | 38 | // I don't know exactly what it does 39 | // acf does it so I copied it 40 | var self = this, 41 | data = this.o; 42 | 43 | // set the ajax action that's set up in php 44 | data.action = 'load_city_field_choices'; 45 | // set the state value to be submitted 46 | data.state = $value; 47 | 48 | // this is another bit I'm not sure about 49 | // copied from ACF 50 | data.exists = []; 51 | 52 | // this the request is copied from ACF 53 | this.state_request = $.ajax({ 54 | url: acf.get('ajaxurl'), 55 | data: acf.prepare_for_ajax(data), 56 | type: 'post', 57 | dataType: 'json', 58 | async: true, 59 | success: function(json){ 60 | // function to update the city field choices 61 | 62 | // get the city field 63 | // the city field key that we want to update 64 | var $select = $('[data-key="field_5793770922131"] select'); 65 | 66 | // add options to the city field 67 | for (i=0; i'+json[i]['label']+''; 69 | $select.append($item); 70 | } 71 | } 72 | }); 73 | }, 74 | }); 75 | 76 | // triger the ready action on page load 77 | $('[data-key="field_579376f522130"] select').trigger('ready'); 78 | }); -------------------------------------------------------------------------------- /dynamic-select-example/my-acf-extension.php: -------------------------------------------------------------------------------- 1 | ID) || 29 | (get_post_type($post->ID) != 'post' && get_post_type($post->ID) != 'city')) { 30 | return $field; 31 | } 32 | 33 | // get states 34 | $args = array( 35 | 'post_type' => 'state', 36 | 'posts_per_page' => -1, 37 | 'orderby' => 'title', 38 | 'order' => 'ASC' 39 | ); 40 | $query = new WP_Query($args); 41 | $choices = array('' => '-- State --'); 42 | if (count($query->posts)) { 43 | // populate choices 44 | foreach ($query->posts as $state) { 45 | $choices[$state->ID] = $state->post_title; 46 | } 47 | } 48 | $field['choices'] = $choices; 49 | return $field; 50 | } // end public function load_state_field_choices 51 | 52 | public function load_city_field_choices($field) { 53 | // this function dynamically loads city field choices 54 | // based on the currently saved state 55 | 56 | // I only want to do this on Posts 57 | global $post; 58 | if (!$post || 59 | !isset($post->ID) || 60 | get_post_type($post->ID) != 'post') { 61 | return $field; 62 | } 63 | // get the state post id 64 | // I generally use get_post_meta() instead of get_field() 65 | // when building functionality, but get_field() could be 66 | // subsitited here 67 | $state = intval(get_post_meta($post->ID, 'state', true)); 68 | $cities = $this->get_cities($state); 69 | $field['choices'] = $cities; 70 | return $field; 71 | } // end public funciton load_city_field_choices 72 | 73 | public function enqueue_script() { 74 | // enqueue acf extenstion 75 | 76 | // only enqueue the script on the post page where it needs to run 77 | /* *** THIS IS IMPORTANT 78 | ACF uses the same scripts as well as the same field identification 79 | markup (the data-key attribute) if the ACF field group editor 80 | because of this, if you load and run your custom javascript on 81 | the field group editor page it can have unintended side effects 82 | on this page. It is important to alway make sure you're only 83 | loading scripts where you need them. 84 | */ 85 | global $post; 86 | if (!$post || 87 | !isset($post->ID) || 88 | get_post_type($post->ID) != 'post') { 89 | return; 90 | } 91 | 92 | $handle = 'my-acf-extension'; 93 | $version = acf_get_setting('version'); 94 | if (version_compare($acf_version, '5.7.0', '<')) { 95 | // I'm using this method to set the src because 96 | // I don't know where this file will be located 97 | // you should alter this to use the correct fundtions 98 | // to set the src value to point to the javascript file 99 | $src = '/'.str_replace(ABSPATH, '', dirname(__FILE__)).'/my-acf-extension.js'; 100 | } else { 101 | $src = '/'.str_replace(ABSPATH, '', dirname(__FILE__)).'/dynamic-select-on-select.js'; 102 | } 103 | // make this script dependent on acf-input 104 | $depends = array('acf-input'); 105 | 106 | wp_enqueue_script($handle, $src, $depends); 107 | } // end public function enqueue_script 108 | 109 | public function ajax_load_city_field_choices() { 110 | // this funtion is called by AJAX to load cities 111 | // based on state selecteion 112 | 113 | // we can use the acf nonce to verify 114 | if (!wp_verify_nonce($_POST['nonce'], 'acf_nonce')) { 115 | die(); 116 | } 117 | $state = 0; 118 | if (isset($_POST['state'])) { 119 | $state = intval($_POST['state']); 120 | } 121 | $cities = $this->get_cities($state); 122 | $choices = array(); 123 | foreach ($cities as $value => $label) { 124 | $choices[] = array('value' => $value, 'label' => $label); 125 | } 126 | echo json_encode($choices); 127 | exit; 128 | } // end public function ajax_load_city_field_choices 129 | 130 | private function get_cities($post_id) { 131 | // $post_id is post ID of state post 132 | // get all cities related to the state 133 | $args = array( 134 | 'post_type' => 'city', 135 | 'posts_per_page' => -1, 136 | 'orderby' => 'title', 137 | 'order' => 'ASC', 138 | 'meta_query' => array( 139 | array( 140 | 'key' => 'state', 141 | 'value' => $post_id 142 | ) 143 | ) 144 | ); 145 | $query = new WP_Query($args); 146 | $choices = array('' => '-- City --'); 147 | if (count($query->posts)) { 148 | // populate choices 149 | foreach ($query->posts as $post) { 150 | $choices[$post->ID] = $post->post_title; 151 | } 152 | } 153 | return $choices; 154 | } // end private function get_cities 155 | 156 | } // end class my_acf_extension 157 | 158 | ?> 159 | -------------------------------------------------------------------------------- /dynamic-text-based-on-user-select/README.md: -------------------------------------------------------------------------------- 1 | # ACf Text Fields based on User field 2 | 3 | ***This example only workds in ACF5*** 4 | 5 | This repo is an example of how to dynamically load some ACF text fields based on the selection made in 6 | another user type field. This is another example can be used as the basis of understanding how to 7 | extend ACF to use AJAX for the creation of dyanmic fields. Every field type in ACF will need slightly 8 | different code to get this to work. This is presented in the hope that it will help others to better 9 | understand how to extend the functionality of ACF. 10 | 11 | Please note that this will only work with a user field that allows a single selection. 12 | 13 | See the comments in the files for comments about the code and what it is doing. Please note that the 14 | PHP in this example uses OOP rather than simple functions. 15 | 16 | In this example I have set up a field group that includes a User Type field. The field group is attached 17 | to the "Post" post type. The JSON file is an export of this field group from ACF. You can import this 18 | if you want to test this example. You'll need to populate some users on your site to see it in action. 19 | 20 | For the rest of the explanation see the comments in the PHP and JS files 21 | -------------------------------------------------------------------------------- /dynamic-text-based-on-user-select/dynamic-text-based-on-user-select.js: -------------------------------------------------------------------------------- 1 | 2 | jQuery(document).ready(function($){ 3 | // make sure acf is loaded, it should be, but just in case 4 | if (typeof acf == 'undefined') { return; } 5 | 6 | // extend the acf.ajax object 7 | // you should probably rename this var 8 | var myUserFieldextension = acf.ajax.extend({ 9 | events: { 10 | // this data-key must match the field key for the user field on the post page where 11 | // you want to dynamically load additional user information 12 | 'change [data-key="field_57ab9d5905e4f"] input': '_update_user_fields', 13 | // this entry is to cause the city field to be updated when the page is loaded 14 | 'ready [data-key="field_57ab9d5905e4f"] input': '_update_user_fields', 15 | }, 16 | 17 | // this is our function that will perform the 18 | // ajax request when the state value is changed 19 | _update_user_fields: function(e){ 20 | 21 | // get the user selection 22 | var $value = e.$el.val(); 23 | 24 | // a lot of the following code is copied directly 25 | // from ACF and modified for our purpose 26 | 27 | // I assume this tests to see if there is already a request 28 | // for this and cancels it if there is 29 | if( this.update_user_request) { 30 | this.update_user_request.abort(); 31 | } 32 | 33 | // I don't know exactly what it does 34 | // acf does it so I copied it 35 | var self = this, 36 | data = this.o; 37 | 38 | // set the ajax action that's set up in php 39 | data.action = 'load_user_details'; 40 | // set the user id value to be submitted 41 | data.user_id = $value; 42 | 43 | // this is another bit I'm not sure about 44 | // copied from ACF 45 | data.exists = []; 46 | 47 | // this the request is copied from ACF 48 | this.update_user_request = $.ajax({ 49 | url: acf.get('ajaxurl'), 50 | data: acf.prepare_for_ajax(data), 51 | type: 'post', 52 | dataType: 'json', 53 | async: true, 54 | success: function(json){ 55 | // function to populate fields 56 | // loop through the values returned 57 | // and insert into fields 58 | for (i=0; iID) || 33 | get_post_type($post->ID) != 'post') { 34 | return; 35 | } 36 | 37 | $handle = 'myUserFieldextension'; 38 | 39 | // I'm using this method to set the src because 40 | // I don't know where this file will be located 41 | // you should alter this to use the correct fundtions 42 | // to set the src value to point to the javascript file 43 | $src = '/'.str_replace(ABSPATH, '', dirname(__FILE__)).'/dynamic-text-based-on-user-select.js'; 44 | // make this script dependent on acf-input 45 | $depends = array('acf-input'); 46 | 47 | wp_enqueue_script($handle, $src, $depends); 48 | } // end public function enqueue_script 49 | 50 | public function ajax_load_user_details() { 51 | // this funtion is called by AJAX to get the user information 52 | // based on user selection 53 | 54 | // we can use the acf nonce to verify 55 | if (!wp_verify_nonce($_POST['nonce'], 'acf_nonce')) { 56 | die(); 57 | } 58 | $user = 0; 59 | if (isset($_POST['user_id'])) { 60 | $user = intval($_POST['user_id']); 61 | } 62 | $values = array(); 63 | $user = get_user_by('id', $user); 64 | if ($user) { 65 | $values= array( 66 | // these are the field keys that match were we want to show the values 67 | array( 68 | 'key' => 'field_57ab9d8305e50', 69 | 'value' => $user->data->user_login 70 | ), 71 | array( 72 | 'key' => 'field_57ab9de105e54', 73 | 'value' => $user->data->display_name 74 | ), 75 | array( 76 | 'key' => 'field_57ab9df305e56', 77 | 'value' => $user->data->user_email 78 | ) 79 | ); 80 | } 81 | echo json_encode($values); 82 | exit; 83 | } // end public function ajax_load_user_details 84 | 85 | } // end class myUserFieldextension 86 | 87 | ?> 88 | -------------------------------------------------------------------------------- /dynamic-text-based-on-user-select/group_57ab9d41279f5.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57ab9d41279f5", 3 | "title": "Dynamic User Select Example", 4 | "fields": [ 5 | { 6 | "key": "field_57ab9d5905e4f", 7 | "label": "user", 8 | "name": "user", 9 | "type": "user", 10 | "instructions": "Select a User to see their information dynamically populated in the fields below.", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "role": "", 19 | "allow_null": 0, 20 | "multiple": 0 21 | }, 22 | { 23 | "key": "field_57ab9d8305e50", 24 | "label": "Username", 25 | "name": "username", 26 | "type": "text", 27 | "instructions": "", 28 | "required": 0, 29 | "conditional_logic": 0, 30 | "wrapper": { 31 | "width": "", 32 | "class": "", 33 | "id": "" 34 | }, 35 | "default_value": "", 36 | "placeholder": "", 37 | "prepend": "", 38 | "append": "", 39 | "maxlength": "", 40 | "readonly": 0, 41 | "disabled": 0 42 | }, 43 | { 44 | "key": "field_57ab9de105e54", 45 | "label": "Display Name", 46 | "name": "display_name", 47 | "type": "text", 48 | "instructions": "", 49 | "required": 0, 50 | "conditional_logic": 0, 51 | "wrapper": { 52 | "width": "", 53 | "class": "", 54 | "id": "" 55 | }, 56 | "default_value": "", 57 | "placeholder": "", 58 | "prepend": "", 59 | "append": "", 60 | "maxlength": "", 61 | "readonly": 0, 62 | "disabled": 0 63 | }, 64 | { 65 | "key": "field_57ab9df305e56", 66 | "label": "Email", 67 | "name": "email", 68 | "type": "email", 69 | "instructions": "", 70 | "required": 0, 71 | "conditional_logic": 0, 72 | "wrapper": { 73 | "width": "", 74 | "class": "", 75 | "id": "" 76 | }, 77 | "default_value": "", 78 | "placeholder": "", 79 | "prepend": "", 80 | "append": "" 81 | } 82 | ], 83 | "location": [ 84 | [ 85 | { 86 | "param": "post_type", 87 | "operator": "==", 88 | "value": "post" 89 | } 90 | ] 91 | ], 92 | "menu_order": 0, 93 | "position": "normal", 94 | "style": "default", 95 | "label_placement": "top", 96 | "instruction_placement": "label", 97 | "hide_on_screen": "", 98 | "active": 1, 99 | "description": "", 100 | "modified": 1470867204 101 | } -------------------------------------------------------------------------------- /repeater-ajax-load-more/README.md: -------------------------------------------------------------------------------- 1 | # ACf Repeater Load More Example 2 | 3 | This example shows the basics of adding load more functionality for a repeater field. 4 | 5 | Read over all the files for explanation 6 | -------------------------------------------------------------------------------- /repeater-ajax-load-more/group_57cae2b099966.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "group_57cae2b099966", 3 | "title": "Repeater Load More Example", 4 | "fields": [ 5 | { 6 | "key": "field_57cae2c4c1227", 7 | "label": "load_more_example_repeater", 8 | "name": "load_more_example_repeater", 9 | "type": "repeater", 10 | "instructions": "", 11 | "required": 0, 12 | "conditional_logic": 0, 13 | "wrapper": { 14 | "width": "", 15 | "class": "", 16 | "id": "" 17 | }, 18 | "collapsed": "", 19 | "min": "", 20 | "max": "", 21 | "layout": "table", 22 | "button_label": "Add Row", 23 | "sub_fields": [ 24 | { 25 | "key": "field_57cae2e1c1228", 26 | "label": "sub field", 27 | "name": "sub_field", 28 | "type": "text", 29 | "instructions": "", 30 | "required": 0, 31 | "conditional_logic": 0, 32 | "wrapper": { 33 | "width": "", 34 | "class": "", 35 | "id": "" 36 | }, 37 | "default_value": "", 38 | "placeholder": "", 39 | "prepend": "", 40 | "append": "", 41 | "maxlength": "" 42 | } 43 | ] 44 | } 45 | ], 46 | "location": [ 47 | [ 48 | { 49 | "param": "post_type", 50 | "operator": "==", 51 | "value": "post" 52 | } 53 | ] 54 | ], 55 | "menu_order": 0, 56 | "position": "normal", 57 | "style": "default", 58 | "label_placement": "top", 59 | "instruction_placement": "label", 60 | "hide_on_screen": "", 61 | "active": 1, 62 | "description": "", 63 | "modified": 1473011412 64 | } -------------------------------------------------------------------------------- /repeater-ajax-load-more/repeater-ajax-load-more-template-code.php: -------------------------------------------------------------------------------- 1 | 15 |
    16 | 22 |
  • 23 | 31 |
32 | 39 | style="display: none;">Show More 44 | 47 | 83 | -------------------------------------------------------------------------------- /repeater-ajax-load-more/repeater-ajax-load-more.php: -------------------------------------------------------------------------------- 1 |
  • $count) { 66 | $more = true; 67 | } 68 | // output our 3 values as a json encoded array 69 | echo json_encode(array('content' => $content, 'more' => $more, 'offset' => $end)); 70 | exit; 71 | } // end function my_repeater_show_more 72 | 73 | // this will load the example field group included 74 | // you only need this when setting up this example 75 | // it should be removed if you're using your own field group 76 | add_action('acf/include_fields', 'load_repeater_more_example_group'); 77 | function load_repeater_more_example_group() { 78 | $file = dirname(__FILE__).'/group_57cae2b099966.json'; 79 | $json = file_get_contents($file); 80 | $group = json_decode($json, true); 81 | acf_add_local_field_group($group); 82 | } // end function load_repeater_more_example_group 83 | 84 | ?> -------------------------------------------------------------------------------- /unique-repeater-checkbox/README.md: -------------------------------------------------------------------------------- 1 | # ACF Unique Repeater True/False Field 2 | 3 | ***This examples only works in ACF5*** 4 | 5 | This example shows how to create a true/false field in a repeater that will only allow the field to be 6 | checked in one row of the repeater. It also prevents the currently checked item from being unchecked. 7 | This basically turns the true/false field in the repeater into a multirow radio field. 8 | 9 | To use this example create a repeater that includes a true/false field and then use the field key 10 | to modify the JavaScript file in this example. 11 | 12 | Please note that this does not force one of the true/false fields in one of rows to be checked until 13 | one of them has actually been checked. When I use this if no rows are check then I add code to my PHP 14 | to assume that the field in the first row is checked. 15 | 16 | ***Note: I do not know if this will work with the UI Toggle added to the True/False field in version 5.5.0 of ACF. I have not tested it. My guess is that it will not.** 17 | -------------------------------------------------------------------------------- /unique-repeater-checkbox/unique-repeater-checkbox-acf57.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Updated JS file for ACF >= 5.7.0 4 | See comments in the original file for things not included here 5 | The comments here mainly cover the differences between 6 | ACF < 5.7.0 and ACF >= 5.7.0 7 | */ 8 | 9 | 10 | /* 11 | The first main difference is that we cannot extend 12 | the acf.ajax object because it no longer exists 13 | we have to add our own function on jQuery(document).ready 14 | */ 15 | jQuery(document).ready(function($) { 16 | /* 17 | The next difference is how we target and detect changes to the field 18 | Since a repeater can be dynamically added we must target the document 19 | and include the selector for the checkbox field 20 | */ 21 | $(document).on('change', '[data-key="field_5b6c628cb8088"] .acf-input input', function(e) { 22 | /* 23 | The final difference is how we get the checkbox that was actually clicked 24 | */ 25 | var target = $(e.target); 26 | /* 27 | From this point on the logic is identical to the original JS 28 | */ 29 | var checked = target.prop('checked'); 30 | if (!checked) { 31 | return; 32 | } 33 | var id = target.prop('id'); 34 | var key = target.closest('.acf-field').attr('data-key'); 35 | var list = $('[data-key="'+key+'"] input').not('[data-key="'+key+'"] input[type="hidden"]').not('.acf-clone [data-key="'+key+'"] input'); 36 | if (list.length == 1) { 37 | return; 38 | } 39 | for (var i=0; i