Home › Forums › General Issues › Extending acf_field_image
Hi,
I’d like to create a field that stores a single image alongside a single x/y position. Rather than completely rewrite everything, I’d like to extend the image field so I can take advantage of the existing default behaviour and media picker.
I was able to extend the field without a problem except that when the ‘Add Image’ button is clicked when editing a post, nothing happens – I would have expected the media picker to be opened. Here’s a stripped down version of the code.
I assume that because the field uses a different name (i.e. not ‘image’) I’m breaking a dependancy or something
// exit if accessed directly
if( ! defined( 'ABSPATH' ) ) exit;
// check if class already exists
if( !class_exists('my_new_field') ) :
class my_new_field extends acf_field_image {
function __construct( $settings ) {
// do not delete!
acf_field::__construct();
//parent::__construct();
}
function initialize() {
parent::initialize();
$this->name = 'my_new_field';
$this->label = __('My New Field', 'my_new_field');
}
function render_field( $field ) {
parent::render_field($field);
$atts_x = array('type' => 'text', 'readonly' => 'readonly', 'name' => $field['name'] . '[x]', 'value' => $field['value']['x']);
echo acf_get_text_input($atts_x);
$atts_y = array('type' => 'text', 'readonly' => 'readonly', 'name' => $field['name'] . '[y]', 'value' => $field['value']['y']);
echo acf_get_text_input($atts_y);
}
}
new my_new_field($this->settings);
endif;
There are several problems with doing this.
The first is that I don’t think you can extend an existing ACF field in this way, but I’ve never tried so I can’t really say and I don’t know of any examples where this has been done.
The second is that even if you do extend the field there is nowhere in the database for ACF to store these values. An ACF image field stores only the image ID value of the image. Adding inputs to a field that are filled in when editing a page will not cause these values to actually be stored anywhere unless you add code that will take those values and store them in some way.
The only way that you’ll be able to do this is to build a new field type from scratch https://www.advancedcustomfields.com/resources/creating-a-new-field-type/. You can start by copying the existing image field and you’ll need to add your new information and then store the image ID plus your new values as an array of values instead of just storing the image ID. In acf each field has only one value stored in the database.
Hi @hube2,
Yeah, I didn’t include all the functions I’ve written as I didn’t want to swamp the thread but I’ve already overridden functions like format_value()
and update_value()
to treat the data as an array and not just an ID. Everything is working in theory, it’s just the interaction with the media library.
Once I’ve got the script working, I might publish it here somewhere as an example of extending a field… interestingly, all fields extend the base acf_field
class and acf_field_image
actually seems to inherit (or more accurately reuse) methods from acf_field_file
. I like the idea of extending classes as it means I can generally ignore a lot of validation/output/save steps.
There are only two things I can think of. 1 is that the scripts aren’t being adding and 2 is that something about your additions are causing it to not work. Are you getting any JS errors?
Figured it out!
There’s a section of assets/js/acf_input.js
that interfaces with the WordPress media library. Unfortunately it specifically targets the image
field type. I ended up copying a big lump of javascript and changing the type identifier… here’s the code that I copied, notice that I just changed the ‘type’ parameter and it all worked.
I’m sure there must be an easier way to reuse this… I assume some field authors have figured out a way to include a media picker in a more subtle way. I reckon this page would be helpful if I had time to investigate
(function($, undefined){
var Field = acf.Field.extend({
type: 'my_new_field',
$control: function(){
return this.$('.acf-image-uploader');
},
$input: function(){
return this.$('input[type="hidden"]');
},
events: {
'click a[data-name="add"]': 'onClickAdd',
'click a[data-name="edit"]': 'onClickEdit',
'click a[data-name="remove"]': 'onClickRemove',
'change input[type="file"]': 'onChange'
},
initialize: function(){
// add attribute to form
if( this.get('uploader') === 'basic' ) {
this.$el.closest('form').attr('enctype', 'multipart/form-data');
}
},
validateAttachment: function( attachment ){
// defaults
attachment = attachment || {};
// WP attachment
if( attachment.id !== undefined ) {
attachment = attachment.attributes;
}
// args
attachment = acf.parseArgs(attachment, {
url: '',
alt: '',
title: '',
caption: '',
description: '',
width: 0,
height: 0
});
// preview size
var url = acf.isget(attachment, 'sizes', this.get('preview_size'), 'url');
if( url !== null ) {
attachment.url = url;
}
// return
return attachment;
},
render: function( attachment ){
// vars
attachment = this.validateAttachment( attachment );
// update image
this.$('img').attr({
src: attachment.url,
alt: attachment.alt,
title: attachment.title
});
// vars
var val = attachment.id || '';
// update val
this.val( val );
// update class
if( val ) {
this.$control().addClass('has-value');
} else {
this.$control().removeClass('has-value');
}
},
// create a new repeater row and render value
append: function( attachment, parent ){
// create function to find next available field within parent
var getNext = function( field, parent ){
// find existing file fields within parent
var fields = acf.getFields({
key: field.get('key'),
parent: parent.$el
});
// find the first field with no value
for( var i = 0; i < fields.length; i++ ) {
if( !fields[i].val() ) {
return fields[i];
}
}
// return
return false;
}
// find existing file fields within parent
var field = getNext( this, parent );
// add new row if no available field
if( !field ) {
parent.$('.acf-button:last').trigger('click');
field = getNext( this, parent );
}
// render
if( field ) {
field.render( attachment );
}
},
selectAttachment: function(){
// vars
var parent = this.parent();
var multiple = (parent && parent.get('type') === 'repeater');
// new frame
var frame = acf.newMediaPopup({
mode: 'select',
type: 'image',
title: acf.__('Select Image'),
field: this.get('key'),
multiple: multiple,
library: this.get('library'),
allowedTypes: this.get('mime_types'),
select: $.proxy(function( attachment, i ) {
if( i > 0 ) {
this.append( attachment, parent );
} else {
this.render( attachment );
}
}, this)
});
},
editAttachment: function(){
// vars
var val = this.val();
// bail early if no val
if( !val ) return;
// popup
var frame = acf.newMediaPopup({
mode: 'edit',
title: acf.__('Edit Image'),
button: acf.__('Update Image'),
attachment: val,
field: this.get('key'),
select: $.proxy(function( attachment, i ) {
this.render( attachment );
}, this)
});
},
removeAttachment: function(){
this.render( false );
},
onClickAdd: function( e, $el ){
this.selectAttachment();
},
onClickEdit: function( e, $el ){
this.editAttachment();
},
onClickRemove: function( e, $el ){
this.removeAttachment();
},
onChange: function( e, $el ){
var $hiddenInput = this.$input();
acf.getFileInputData($el, function( data ){
$hiddenInput.val( $.param(data) );
});
}
});
acf.registerFieldType( Field );
})(jQuery);
I found a solution for this so I thought I would post it here for the next person who comes along.
If you want to extend the existing class, you use this line:
var Field = acf.models.DatePickerField.extend({
It’s used with the date_time_picker
to extend off the date_picker
but with some enhancements.
What I’m doing is writing a ui enhancement to the actual date_picker
(Client wants a button to easily add 30 days to the date), so I’m using that extend snippet and then applying it back to the actual date_picker
. That way my custom code is being run and the date picker popup still works:
(function($) {
var Field = acf.models.DatePickerField.extend({
type: 'date_picker',
// name: 'myprescription_expiration_date',
events: {
'click .date_plus_30': 'onClick'
},
onClick: function( e, $el ){
e.preventDefault();
alert("cat");
}
});
acf.registerFieldType( Field );
})(jQuery);
Totally unrelated to this thread but if you’re wondering what the 'click .date_plus_30': 'onClick'
is referring to, I have already registered a bit of extra ui right after the admin panel date picker with:
add_action('acf/render_field/name=myprescription_expiration_date', array( $this, 'custom_admin_render_field_date_offset'), 15);
public function custom_admin_render_field_date_offset($field)
{
echo '<p><a href="#" class="date_plus_30">Add 30 days</a></p>';
}
The topic ‘Extending acf_field_image’ is closed to new replies.
Welcome to the Advanced Custom Fields community forum.
Browse through ideas, snippets of code, questions and answers between fellow ACF users
Helping others is a great way to earn karma, gain badges and help ACF development!
We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Privacy Policy. If you continue to use this site, you consent to our use of cookies.