Support

Account

Forum Replies Created

  • As far as I know, the hooks available on the front end are the same as the hooks available in the admin and ACF uses the same code for displaying them both, except for the inclusion of form wrappers and other markup.

    Thanks, you’re right. I’ve assumed that these filters are admin panel specific, and I didn’t even check if my assumption is correct πŸ™‚

    There isn’t any filters or actions that will let you add code between fields.

    It would be great if there were actions like acf/before_field, acf/after_field, acf/before_field_grup, acf/after_field_grup. So, can you convert this topic into a feature request?

    Thanks.

  • I’m also worried a little about the future of ACF. The development has slowed down lately, and as the code base is getting bigger also the number of bugs is increasing. In the earlier stage of the development, the ACF plugin was almost bug-free, now it’s not so perfect.

    And I’m afraid that it will not change because ACF has no unit tests, so even if the programmer is a talented guy (and I’m sure that Eliot is) and even if he tries hard to write the code as good as he can (and the code is really clean and well organised) as the codebase is growing the number of bugs will grow too.

    An another problem with ACF is the lack of modularity on the admin panel side (here’s a post where I write more on this topic: https://support.advancedcustomfields.com/forums/topic/does-acf-have-a-js-api/)

    When ACF was released, back in the past, it was the first class solution. It was way better than any custom fields plugin. But I’m afraid that we can have a new leader. Carbon Fields is very promising. It has almost everything that ACF has and more, for example, the feature that I’ve mentioned before: modularity and unit test

  • > I can say that I’ve been using that $_POST variable for a long time. It’s the only way to know what field a file/image is for during the WP upload process.

    Thanks. So maybe let’s convert this post into a feature request? Maybe it would be good to introduce more hooks in order to make this kind of activities easier and more reliable?

  • We made an offtopic anyway, so let me continue because this discussion is quite interesting.

    A picture A code sample is worth a thousand words so let me just paste a link to ACF competitor’s (maybe not yet, but who knows…) site https://carbonfields.net/docs/advanced-topics-templates/ and this: https://github.com/htmlburger/carbon-field-template

    This is awesome! I would love to see such flexibility in ACF6.

  • Thank you very much. I’m doing a similar thing at the moment:
    – I use ACF hooks in order to run callback when an image field value is changed
    – Then I read the image field data,
    – Then I read the EXIF data from the image field,
    – Then I update other fields values based on the EXIF data.

    So, as long as it’s just a matter of updating existing fields, it’s not a big problem. But if I wanted to create some taxonomy fields on the fly, then it would be tricky, because there’s no way to re-render a field and/or a field group.

    I will probably end up with writing my own ACF custom field that will handle taxonomies update. This will be a simpler and a cleaner solution because I want to have as little DOM traversing in my code as possible. I don’t want my code to break if ACF HTML structure will change.

    Thank you for your help anyway! I hope that in future ACF will be more flexible in this area. I would love to see a real, non-DOM-dependant JS API. I would love to be able to render ACF fields anywhere, with a custom HTML templates, etc. But I know how much work it requires. At the moment this part of ACF code is very monolithic and DOM-oriented. I dream of a MVC-like architecture where models and collections are separated from the view layer (like Backbone Models/Collection/Views).

    If it was implemented then ACF could become something more than just a powerful custom fields manager. If ACF has such an API and architecture then It would be even possible to build something like a page builder (like visual composer) on top of ACF!

    Sorry for the offtopic πŸ™‚

    This is a piece of code I wrote:

    
    import $ from 'jquery';
    
    export default () => {
    
        const $$ = {
            template: $('#js-exif-data')
        };
    
        const nonce = $$.template.data('nonce');
    
        // ACF fields identifiers
        const imageInputName = 'acf[field_56d99d7de115e]';
        const orientationInputName = 'acf[field_56d8b43f73de1]';
    
        const allowedExifData = {
            created_timestamp: 'Data',
            iso: 'ISO',
            focal_length: 'Ogniskowa',
            shutter_speed: 'Migawka',
            aperture: 'PrzysΕ‚ona',
            keywords: 'Tagi'
        };
    
        const utils = {
            /**
             * @param {int} exifValue
             * @returns {string} - <code>landscape</code> or <code>portrait</code>
             */
            getOrientationByExifValue(exifValue) {
                const orientations = [{
                    name: 'landscape',
                    values: [1, 2, 3, 4]
                }, {
                    name: 'portrait',
                    values: [5, 6, 7, 8]
                }];
    
                const orientation = orientations
                    .filter(orientation => {
                        return orientation.values.indexOf(exifValue) !== -1;
                    });
    
                return orientation.length === 0 ? 'unknown' : orientation[0].name;
            },
            /**
             * @param {int} attachmentId
             * @returns {Promise}
             */
            loadExifData(attachmentId) {
                return $.ajax({
                    type: 'post',
                    url: acf.o.ajaxurl,
                    data: {
                        nonce,
                        attachmentId,
                        action: 'get_image_meta',
                    }
                });
            },
            /**
             * @returns {Promise}
             */
            getOrientationTerms() {
                return $.ajax({
                    type: 'post',
                    url: acf.o.ajaxurl,
                    data: {
                        nonce,
                        action: 'get_orientation_terms',
                    }
                });
            },
            /**
             * @param {object} response
             */
            renderExifData(response) {
                $.each(response.data, (k, v) => {
    
                    if (!allowedExifData.hasOwnProperty(k)) {
                        return true;
                    }
    
                    // Convert array values to coma-separated string.
                    const value = (function () {
                        if (v.constructor === Array) {
                            return v.join(', ');
                        }
                        return v;
                    }());
    
                    const $p = $('<li />').html(<code><strong>${allowedExifData[k]}</strong>: ${value}</code>);
                    $$.template.append($p);
    
                });
            },
            /**
             * @param {object} response
             */
            setOrientation(response) {
    
                if (!response.data.hasOwnProperty('orientation')) {
                    return;
                }
    
                utils.getOrientationTerms().done(termsResponse => {
    
                    const wpTerms = termsResponse.data;
    
                    if (wpTerms.constructor !== Array) {
                        return;
                    }
    
                    const exifOrientation = parseInt(response.data.orientation, 10);
                    const orientationHumanReadable = utils.getOrientationByExifValue(exifOrientation);
                    const wpTerm = wpTerms.filter(term => {
                        return term.name === orientationHumanReadable;
                    });
    
                    if (wpTerm.length === 0) {
                        return;
                    }
    
                    const orientationTermId = parseInt(wpTerm[0].id, 10);
                    const $radios = $(<code>input[name=&quot;${orientationInputName}&quot;]</code>);
    
                    const $radio = $radios.filter((index, el) => {
                        return parseInt($(el).val(), 10) === orientationTermId;
                    });
    
                    $radio
                        .prop('checked', true)
                        .trigger('click')
                });
            }
        };
    
        const handlers = {
            imageOnLoad() {
                const $input = $(<code>input[name=&quot;${imageInputName}&quot;]</code>);
                const attachmentId = $input.val();
    
                if ($input.length === 0 || attachmentId === '') {
                    return;
                }
    
                $$.template.empty();
                utils.loadExifData(attachmentId).done(utils.renderExifData);
            },
            /**
             * @param {object} $input - an array of jQuery DOM objects
             */
            imageInputOnChange($input) {
                const attachmentId = $input.val();
    
                if ($input.context.name !== imageInputName) {
                    return;
                }
    
                $$.template.empty();
    
                if (attachmentId === '') {
                    return;
                }
    
                utils.loadExifData(attachmentId).done(response => {
                    utils.renderExifData(response);
                    utils.setOrientation(response);
                })
    
            }
        };
    
        acf.add_action('change', handlers.imageInputOnChange);
        acf.add_action('load', handlers.imageOnLoad);
    }
    
  • Thank you, John. You’ve confirmed my concerns – there’s no JS API.

    So I will probably update custom field value via AJAX, but I still need some way to re-render the field group with updated values – so basically I need to know how ACF handles field group rendering.

    I’m almost sure that it is possible (even if it’s not documented) because this is what happens if a field group is shown conditionally (for example if a field group is assigned to a specific page template and show/hides depending on a chosen page template).

    TL;DR
    I’d like to know how to call a field group re-render (or even better – a single field re-render) with JS.

    Thanks.

  • > You only have 1 field group created on this site?

    No, I meant that I have only one field group assigned to this post type.

    And you were right. An another fields group (a field group containing a clone field) was assigned to an unexisting post. This caused the issue. I did it because I wanted to prevent a clone group from showing anywhere. I’ve even opened a feature request regarding this issue.

    I’ve resolved this issue changing setting the clone field group to inactive and removing a condition that can never be meet.

    But anyway, I think this is a bug that should be fixed. Even if a field group is assigned to an unexisting post then no error or notice should be displayed.

    Thanks for your help.

  • Only one field group is assigned and there’s only one simple rule: post_type === 'case', as I mentioned in the previous post. There’s no magic in the code, no custom filters/action, fields are registered with ACF GUI (not with PHP) and stored in the acf-json directory. ACF is the only plugin I use at the moment. So for me it looks like a bug.

    I can send you a PM with compressed acf-json directory. Will it be helpfull?

  • No, I have only one rule: post_type === 'case'. But even if you were right then a PHP notice shouldn’t be displayed and it would still be a bug.

  • I’m not sure if I understand what are you trying to do. It seems that you want to:

    1. Create an URL field
    2. Download an image from a remote host and push it to the WP media library
    3. Set this image as a post featured image.

    It is possible, but it’s not really an ACF-related issue.

    You have to
    1. use ACF hooks (or even a native WordPress save_post hook)
    2. When a post gets saved you have to get remote image url (get_field('your_image_field', $postId))
    3. Download the file from a remote server (for example with file_get_contents()), save it in a temporary location
    4. Use wp_insert_attachment() in order to push it into the media library. There’s a code sample in the Codex: https://codex.wordpress.org/Function_Reference/wp_insert_attachment

    Yes, I know it’s overcomplicated. But this is the only solution. WordPress images handling sucks so much…

  • Thanks. This is a good workaround, a much more elegant workaround than I used to use (I used to set conditions that could never be met in order not to show a field group on any page, like assigning a field group to a widget), but it’s still a workaround and it might be confusing because it makes a field group marked inactive on the list, when in fact, it’s not inactive.

    So it would be great if a field group could be left unassigned.

  • why not simply assign the same field group to the various option pages?

    You’re right. Thanks for a tip. I have no idea why I did it this way πŸ™‚

  • Thank you very much for the detailed explanation. Now I understand what happens, but IMO it shouldn’t be done this way. So it’s not a bug, but a weird, unintuitive behaviour.

    Using get_post_meta() is a good solution. I didn’t even try to use get_post_meta() because I thought that the value is not saved in the custom field. I thought it’s a bug.

    It would be good if it was documented.

  • I would expect the new value in the second test case as well.

    IMO The fact that taxonomy term is not saved shouldn’t make any difference.
    The value of the custom field is saved no matter if a taxonomy term is saved or not. So I would expect the new custom field value in the acf/save_post filter. This is a value of a custom field, not a value of a taxonomy term.

  • I think you get me wrong. I’m not saying that the taxonomy term is not saved. The values are saved properly (for the custom field and for the taxonomy term as well). This is not the issue.

    I’m saying that the value of the taxonomy custom field is wrong when I try to get its value in the acf/save_post callback. When the value of the priority parameter is set to 20 then in the acf/save_post I should receive the newly set CF value, but instead of a new value, I get the previous one.

    
    add_action('acf/save_post', function ($postId) {
    
        get_field('image-field', $postId); // Returns the correct value
        get_field('taxonomy-field', $postId); // Returns the same value as if the priority parameter was set to 10
    
    }, 20 ); // Notice the priority parameter
    
  • Yes. I’ve also set save terms to true but only load terms causes the issue.

  • Hey,

    I’m not looking for help πŸ™‚ I’ve found a solution already (not a clean solution, but at least it works). It was easy. I just wanted to share the knowledge. For many people, it might not be so straightforward because there’s no documentation for ACF JS objects.

    Here’s the another example where I make use of the acf.fields object.

    But thanks for the links anyway. Sometimes creating a new field type that extends the built-in field might be a better idea than trying to “hack” the existing fields.

    PS
    I would appreciate if @eliot commented on this topic. The only thing I wasn’t able to find out is how to get all the instances of the field of a given type.

    As I said in the previous comment: an acf.fields.{FIELD_TYPE} object contains only a one field of the given type. How to get the other fields?

  • @nml

    There’s an acf.fields.google_map.maps object. It holds the references to the google maps objects. To change a map type for a single field you have to find the field’s key. So let’s assume that the key is acf-field_56d8b333cb295-57371867cb2db.

    So to change the map type simply execute:
    acf.fields.google_map.maps['acf-field_56d8b333cb295-57371867cb2db'].setMapTypeId(google.maps.MapTypeId.TERRAIN)

    You can replace the TERRAIN with: ROADMAP, SATELLITE or HYBRID.

    PS
    I don’t where does the key name come from. 56d8b333cb295 is a field key. But I have no idea what does the second part (57371867cb2db) stand for.

    <b>// EDIT:</b>

    I forgot to say about one important detail. You have to run this code after the google maps API is initialized. As far I know there’s no public hook, so the only way is to run this code inside setIntervalI() and check acf.fields.google_map.status value is ready;

    Example:

    acf.add_action('load', function () {
    
        var googleMap = acf.fields.google_map;
    
        var interval = setInterval(function () {
    
            if (googleMap.status !== 'ready') {
                return;
            }
    
            clearInterval(interval);
    
            // Set all the google maps type to HYBRID
            $.each(googleMap.maps, (keyName, map) => {
                map.setMapTypeId(google.maps.MapTypeId.HYBRID);
            });
    
        }, 100);
    
    });

    PS
    @eliot
    I hope you read this topic. I have an idea how to improve the core code a bit. It would be great if acf.fields.google_map.status wasn’t just a regular field. IMO it would be much better if it returned a promise (an instance of $.Deferred()) object.

    It’s a much cleaner solution. setInterval() wouldn’t be required then.

    Let’s assume that there’s a method like acf.fields.google_map.loadingGoogleMap(). It returns a promise. Consider the following pseudo-implementation:

    acf.fields.google_map.loadingGoogleMap = function () {
    
        var deferred = $.Deferred();
    
        $.getScript('http://google.maps.url.js')
            .done(function () {
    
                // Google map initialization code goes here...
    
                deferred.resolve(); 
    
            })
            .fail(function () {
                deferred.reject();
            });
    
        return deferred;
    
    };

    And here’s the modified initial code. Now setInterval() is not required.

    acf.add_action('load', function () {
    
        var googleMap = acf.fields.google_map;
    
        // This code runs when the 'acf.fields.google_map.loadingGoogleMap' promise is resolved
        acf.fields.google_map.loadingGoogleMap.done(function () {
    
            // Set all the google maps type to HYBRID
            $.each(googleMap.maps, (keyName, map) => {
                map.setMapTypeId(google.maps.MapTypeId.HYBRID);
            });
    
        });
    
    });

    Simple. Isn’t it?

    PPS:
    Yes. I know, the promise should be resolved in the google maps callback function, not in the $.getScript().done() callback. This is why I called it pseudo-implementation πŸ™‚

  • @hube2

    A little update. I’ve just found that ACF exposes a global acf JS object. So instead of DOM traversing we’re able to use its fields and methods to get the fields data.

    For example: acf.fields.google_map is an object containing a google_map field type data, a reference to the DOM element, fields settings, etc.

    But it returns only one field of a given type (the last one). I didn’t find yet how to get the data of a specific field of a given type. I’ll try to figure it out and I’ll update this post.

  • Hi @elliot,
    Sorry for not responding to your previous message. I didn’t notice your post.

    It’s a great news! Thank you for implementing this feature in the ACF core. Your solution is much simpler than mine. I’ve completely forgotten about the native wp_cache and tried to re-invent the wheel introducing my own caching class πŸ™‚

  • I should have been more specific. Here’s the explanation:

    This is how I used to register options pages:

    
    acf_add_options_page([
        'page_title' => __('Options page', 'textdomain'),
        'menu_title' => __('Options page', 'textdomain'),
        'menu_slug' => 'options-page-slug'
    ]);
    

    This is what I refer to in the previous post:

    
    acf_add_options_page([
        'page_title' => __('Options page', 'textdomain'),
        'menu_title' => __('Options page', 'textdomain'),
        'menu_slug' => 'options-page-slug',
        'post_id' => 'custom-id-as-a-string' // A custom string
    ]);
    

    Thanks to this feature I don’t need to prefix custom fields names with a unique prefix, in order to avoid conflicts. I can simply register multiple options pages with a different post_id parameter.

    Then I’m able to get custom fields like this:

    
    get_field('field_name', 'custom-id-as-a-string');
    

    Direct answer to your question:
    Fields are saved in the wp_options table, but their names are prefixed with a value of the post_id attribute. So if the field name is field_name and the options page post_id attribute value is custom-id-as-a-string then the database entry is custom-id-as-a-string_field_name.

    I think it should be clearly explained in the documentation. It is said in the documentation that the post_id could be an integer or a string, but it doesn’t explain that it can be any string. Users might think that the string should be a post slug or something… This is exactly what I thought before i started to play with custom strings.

  • Of course – at the end you have to update the DOM somehow. But if there were a model containing all the ACF data it wouldn’t interfere with anything, you sill would be able to traverse the DOM directly, but you would be able to traverse the model as well. The model should store references to DOM elements.

    The biggest problem about DOM traversing is relying on document structure, which may change (because of changes in ACF or in WP core).

    Another abstraction layer would make the logic not dependent on document structure.

    OK, I made a huge off-topic πŸ™‚

  • So you see my point πŸ™‚

    There’s a lot of DOM traversing in your code. But the issue is not only related to the lack of data in the ACF callbacks but also to the way how ACF JS code is designed – it relies strongly on DOM.

    It would be great if ACF code relied on a data model instead of DOM. But it would require a strong ACF core refactoring. I wish Backbone models and collections were used.

Viewing 25 posts - 1 through 25 (of 55 total)