$(...).concreteFileSelector is not a function

I have this in my javascript code : $(‘[data-file-selector=“kysymys_edit_kuva”]’).concreteFileSelector({“inputName”: “q1”, “fID”: 123, “canReplaceFile”: false, “filters”: [{“field”: “type”, “type”: 1}], “chooseText”: "select image "}); and this worked fine with version 8. But in concrete v9 i get this error $(…).concreteFileSelector is not a function. Can someone know how to solve this? Thanx :slight_smile:

Try requiring the “core/cms” asset where that feature is being used.

The javascript behind the file selector widget changed between v8 and v9. You can find the source of the new widget and how the JavaScript is built in:
concrete/src/Application/Service/FileManager.php

I can see how javascript is build there, but is there some example to how call file selector using javascript?

Unfortunately no documentation on that JavaScript. You will need to read the code, dig through github, and work it out backwards. Also look at blocks that use the file picker such as the core image gallery, core slider, and the image picking plugin for the rich text editor.

Any idea why there is no documentation on that javascript?

There are many areas of the core code that could benefit from up-to-date documentation, or better documentation, or just some documentation. Documentation is the main thing that holds Concrete CMS back as an open source project.

Functionality moves ahead. The internal structure becomes more complex. Documentation is left further behind. It hinders those wanting to develop sites and hinders developers from getting involved in anything more than superficial core updates.

@JohntheFish documentation is always helpful. For folks visiting this thread, contributions can be made here for things that might not be documented yet:

1 Like

@EvanCooper, whilst that is fine in theory, the problem is often that there is insufficient starting point in the existing docs for others to add to.

As per this particular case, there is no documentation to start from for the v9 javascript widgets. Some of us know that they mostly now use vue.js. But by analogy you could document the whole of Concrete CMS by simply saying “its written in php”. How the core JavaScript widgets work could be a good topic for a Friday video (but don’t let that be an excuse for writing anything down).

On a tangent, can you get Standard Widgets :: Legacy Documentation copied across to the current docs. The only change I know of as a starting point is correcting

$form = Loader::helper('form');
to
$form = $app->make('helper/form');

, as per Form Widget Reference - Which the new page should be linked from/to.

Thanks John - GMTA I just nominated that topic for FoF and then saw your suggestion :+1: agreed, that would be helpful.

I will take a look at that widgets section as well.

@JohntheFish @siavash here’s a shim I’ve been using to update old blocks. This is an an abridged snippet of the relevant JavaScript in auto.js prior to upgrading:

Concrete.event.bind('btCtaBlock.ctaItem.edit.open', function (options, settings) {
    var updateItem = function (newField) {
        $(newField).find(".launch-tooltip").tooltip({container: "#ccm-tooltip-holder"});
        var ftImageThumbnail = $(newField).find('.ft-image-thumbnail-file-selector');
        if ($(ftImageThumbnail).length > 0) {
            $(ftImageThumbnail).concreteFileSelector({'inputName': $(ftImageThumbnail).attr('data-file-selector-input-name'), 'filters': [], 'fID' : $(ftImageThumbnail).attr('data-file-selector-f-id') });
        }
    };

    $(document).ready(function () {
            $.each(items, function (i, v) {
                newField = $(sortableItems).find('.sortable-item[data-id="' + v.id + '"]');
                updateItem(newField);
            });
        }
    });

});

It’s mostly similar to OP’s example, specifically line 6.

Now here’s the revised code which works with version 9 (again, this is abridged to show hopefully just mostly relevant code; I cut out a lot really quickly though, so just use this as a guide):

// shim to make concreteFileSelector available with the right context
let concreteFileSelector = function () {}
Concrete.Vue.activateContext('cms', function (Vue, cfg) {
    let selectorId = 0;
    concreteFileSelector = function (e, config) {
        const selectorClass = 'file-selector-' + selectorId++
        $(e).addClass(selectorClass).html(`
                <concrete-file-input 
                    choose-text="Choose File"
                    input-name="${config.inputName}"
                    file-id="${config.fID}"
                    :filters='${JSON.stringify(config.filters)}'>
                </concrete-file-input>
            `);
        new Vue({el: `.${selectorClass}`, components: cfg.components});
    };
});

// modified code
Concrete.event.bind('btCtaBlock.ctaItem.edit.open', function (options, settings) {
    var updateItem = function (newField) {
        var ftImageThumbnail = $(newField).find('.ft-image-thumbnail-file-selector');
        if ($(ftImageThumbnail).length > 0) {
            // here we call the function directly rather than as a jQuery extension, so we 
            // need to pass in the element first then an object containing the options
            concreteFileSelector(
                ftImageThumbnail, {
                    'inputName': $(ftImageThumbnail).attr('data-file-selector-input-name'),
                    'filters': [], 'fID' : $(ftImageThumbnail).attr('data-file-selector-f-id')
                }
            );
        }
    };

    $(document).ready(function () {
            // finally you _may_ need to use this setTimeout hack if your code is trying to 
            // call the function before everything is ready to go. This may not be necessary 
            // depending on your code.
            setTimeout(function (items, sortableItems) {
                $.each(items, function (i, v) {
                    newField = $(sortableItems).find('.sortable-item[data-id="' + v.id + '"]');
                    updateItem(newField);
                });
            }, 0, items, sortableItems);
        }
    });

});

I think @EvanCooper is putting together something that can more fully explain what’s going on, but this example might help in the meantime.

1 Like