XF 2.0 [JS] XF.proxy is not a function

Lukas W.

Well-known member
Some users of my profile cover add-on are currently stumbling over this error:
JavaScript:
profile-cover.js?_v=cb4f5ac7:43 Uncaught TypeError: XF.proxy is not a function
    at c.init (profile-cover.js?_v=cb4f5ac7:43)
    at c (core-compiled.js?_v=cb4f5ac7:115)
    at e (core-compiled.js?_v=cb4f5ac7:116)
    at HTMLFormElement.<anonymous> (core-compiled.js?_v=cb4f5ac7:116)
    at Function.each (jquery.min.js:2)
    at r.fn.init.each (jquery.min.js:2)
    at Object.f [as initialize] (core-compiled.js?_v=cb4f5ac7:116)
    at Object.activate (core-compiled.js?_v=cb4f5ac7:43)
    at b.__construct (core-compiled.js?_v=cb4f5ac7:97)
    at new b (core-compiled.js?_v=cb4f5ac7:68)

I'm a bit confused why this is happening though. The file in question is essentially a copy from js/xf/avatar.js that carries modifications for the profile cover format and replacement procedures. I've tried Chrome, Firefox, Internet Explorer and Edge, but I cannot reproduce the error. As the function in question is a core function and I've used the non-modified avatar.js before without any trouble (or at least no reported trouble) I can't really imagine why this error would happen now.

The full content of the javascript file is:
JavaScript:
!function ($, window, document, _undefined) {
    "use strict";

    // ################################## PROFILE COVER UPLOAD HANDLER ###########################################
    XF.updateProfileCover = function (userId, newProfileCover, cropX, cropY) {
        $('.profile-cover').each(function () {
            let $profileCoverContainer = $(this),
                $cover = $profileCoverContainer.find('img, span').first(),
                $newProfileCover = $(newProfileCover);

            $profileCoverContainer.html($newProfileCover.html());
        });

        let img = $(newProfileCover).find('img'),
            container = $(newProfileCover);

        console.log($(".hasCover[data-user=" + userId + "]"));

        $(".hasCover[data-user=" + userId + "]").css({
            'background-image': 'url(\'' + img.attr('src') + '\')',
            'background-position': (-cropX) + 'px ' + (-cropY) + 'px',
            'background-size': 'auto'
        });
    };

    XF.ProfileCoverUpload = XF.Element.newHandler({

        options: {},

        init: function () {
            let $form = this.$target,
                $file = $form.find('.js-uploadProfileCover'),
                $cover = $form.find('.js-profileCover'),
                $deleteButton = $form.find('.js-deleteProfileCover');

            if ($cover.find('img').length) {
                $deleteButton.show();
            }
            else {
                $deleteButton.hide();
            }

            $file.on('change', XF.proxy(this, 'changeFile'));
            $form.on('ajax-submit:response', XF.proxy(this, 'ajaxResponse'));
        },

        changeFile: function (e) {
            if ($(e.target).val() !== '') {
                this.$target.submit();
            }
        },

        ajaxResponse: function (e, data) {
            if (data.errors || data.exception) {
                return;
            }

            e.preventDefault();

            if (data.message) {
                XF.flashMessage(data.message, 3000);
            }

            let $form = this.$target,
                $delete = $form.find('.js-deleteProfileCover'),
                $file = $form.find('.js-uploadProfileCover'),
                $cover = $form.find('.js-profileCover'),
                $x = $form.find('.js-profileCoverX'),
                $y = $form.find('.js-profileCoverY'),
                useCustom = ($form.find('input[name="use_custom"]:checked').val() === 1);

            $cover.css({
                left: data.cropX * -1,
                top: data.cropY * -1
            });
            $x.val(data.cropX);
            $y.val(data.cropY);
            $cover.data('x', data.cropX);
            $cover.data('y', data.cropY);

            XF.Element.initializeElement($cover);

            $file.val('');

            XF.updateProfileCover(data.userId, data.profileCover, data.cropX, data.cropY);

            $delete.show();

            $('.js-profileCoverCropper').trigger('profile-cover:updated', data);
        }
    });

    // ################################## PROFILE COVER CROPPER HANDLER ###########################################

    XF.ProfileCoverCropper = XF.Element.newHandler({

        options: {
            size: 96,
            x: 0,
            y: 0
        },

        $img: null,
        size: 96,

        x: 1156,
        y: 96,

        imgW: null,
        imgH: null,

        cropSize: 96,
        scale: 1,

        init: function () {
            this.$target.one('profile-cover:updated', XF.proxy(this, 'profileCoverUpdated'));

            this.$img = this.$target.find('img');

            this.x = this.$target.parent().css('width').match(/\d+/)[0];
            this.y = this.$target.parent().css('height').match(/\d+/)[0];

            if (!this.$img.length) {
                return;
            }

            this.initTest();
        },

        profileCoverUpdated: function (e, data) {
            this.options.x = data.cropX;
            this.options.y = data.cropY;
            this.init();
        },

        initTest: function () {
            let img = this.$img[0],
                tests = 0,
                self = this;

            let test = function () {
                tests++;
                if (tests > 50) {
                    return;
                }

                if (img.naturalWidth > 0) {
                    self.setup();
                }
                else if (img.naturalWidth === 0) {
                    setTimeout(test, 100);
                }
                // if no naturalWidth support (IE <9), don't init
            };

            test();
        },

        setup: function () {
            this.imgW = this.$img[0].naturalWidth;
            this.imgH = this.$img[0].naturalHeight;

            this.cropSize = Math.min(this.imgW, this.imgH);
            this.scale = this.cropSize / this.options.size;

            this.$img.cropbox({
                width: this.x,
                height: this.y,
                zoom: 0,
                maxZoom: 0,
                controls: false,
                showControls: 'never',
                result: {
                    cropX: this.options.x,
                    cropY: this.options.y,
                    cropW: this.x,
                    cropH: this.y
                }
            }).on('cropbox', XF.proxy(this, 'onCrop'));
        },

        onCrop: function (e, results) {
            this.$target.parent().find('.js-profileCoverX').val(results.cropX);
            this.$target.parent().find('.js-profileCoverY').val(results.cropY);
        }
    });

    XF.Element.register('profile-cover-upload', 'XF.ProfileCoverUpload');
    XF.Element.register('profile-cover-cropper', 'XF.ProfileCoverCropper');
}(jQuery, window, document);
 
Last edited:
Line number seems to be very low, I guess script is included out of order. Make sure its included after core, not before.

To be safe you can wrap your code in window.setTimeout(function() { your code here }); that will execute it on next tick after whole file has been loaded.
 
Line number seems to be very low, I guess script is included out of order. Make sure its included after core, not before.
The script is loaded as part of a popup, so I assume it to be highly unlikely that the script is executed anywhere near before core.js.

The load order for the non-popup version seems to be fine too however:
Kq1u9HK.png
 
The script is loaded as part of a popup, so I assume it to be highly unlikely that the script is executed anywhere near before core.js.
Popup is a separate window, it has no access to scripts from parent window, so if it is a proper popup, make sure XenForo scripts are included there as well.
 
Which version of XF are they using? XF.proxy was only added in 2.0.3 so if they were using a version below that, that function wouldn’t exist. You’d have to use $.proxy or they should upgrade.
 
Popup is a separate window, it has no access to scripts from parent window, so if it is a proper popup, make sure XenForo scripts are included there as well.
Talking about a XenForo popup here, the modal, not a different browser window.
 
Top Bottom