Reuse Uploaded Images

Reuse Uploaded Images

icefieldicefield Posts: 45Questions: 19Answers: 1

Saw that other's have had the need to reuse previously uploaded images. (e.g., https://datatables.net/forums/discussion/comment/104347/#Comment_104347).

I'd just like to add my request to have a gallery picker feature added to the Editor. Is something along those lines close in on your radar? Thanks.

This question has an accepted answers - jump to answer

Answers

  • allanallan Posts: 63,488Questions: 1Answers: 10,467 Site admin

    Thanks for adding your vote to this feature. It isn't something we are planning on working on in the near future, but it is on our list.

    Allan

  • icefieldicefield Posts: 45Questions: 19Answers: 1

    Fair enough.

    Wondering if in the not too distance future an alternative means of allowing this might be to allow optional third button to upload field of editor. Something along the lines of "Choose File...", "Clear", "Custom" (where "Custom" text is customizable).

    If custom is selected, a user-defined function is called, and we could show info about previously loaded files as a modal popup on top of the editor. The return from that custom user-defined function would be either to do nothing, or populate the upload field with the index to a previously uploaded file.

    Maybe this saves you some effort building a gallery picker, but offers us a chance to put one together.

    Just a thought.

  • allanallan Posts: 63,488Questions: 1Answers: 10,467 Site admin
    Answer ✓

    Its not a bad idea - thanks for the suggestion. The way I'd probably approach this myself at the moment is to build a library table of images (i.e. a DataTables + Editor for just the list of images available) and then use a select list of those images in the Editor that will consume those images. Then the end user can select from images which are already uploaded. You could also use Select2 if you wanted to actually display the images in the select list rather than just use a file name (which you'd be limited to with a regular select).

    It means your users would need to enter a different form in order to upload new images, but that might (or might not) be acceptable and can be implemented immediately.

    Allan

  • icefieldicefield Posts: 45Questions: 19Answers: 1
    edited May 2020

    I'm revisiting this now that I have a bit of time to tackle. I'm attempting to use Select2, as you mentioned above.

    Here is the server-side PHP code that returns the id of an image. The image is stored in an 'UploadedFile' table, and the id of the image is part of a record in the 'Activity' table:

            $data = Editor::inst($db, 'Activity', '_id')
                ->fields(
                    Field::inst('Activity._idEvent')->setValue($idEvent),
                    Field::inst('Activity._id'),
                    
                    Field::inst('Activity._idImage')
                        ->upload(Utilities::uploadImage( $db, $uniqueId, $idEvent, '/activities' ))
                        ->options(Options::inst()
                            ->table('UploadedFile')
                            ->value('_id')
                            ->label('fileName')
                            ->where( function ($q) {
                                $q->where( 'fileExtension', 'png', '=' );
                            })
                        )
                        ->validator(Validate::dbValues()),
                        
                        // other fields here
                        <snip><snip><snip?
                )
                ->where(function ($q) use( $uniqueId, $idEvent ) {
                    $q->where('Activity._idEvent', $idEvent);
                })
                ->leftJoin('UploadedFile', 'UploadedFile._id', '=', 'Activity._idImage')
                ->process($_POST)
                ->data();
    
                // other app-specific processing here
                <snip><snip><snip>
            }
    
            return json_encode( $data );
        }
    

    That works well to show the Activity records in table.

    To display the image in the table I'm using the following snippet of render code on the client-side for the 'Activity._idImage' column:

    {
        data: 'Activity._idImage',
        render: function (data, type) {
            if (typeof data !== 'undefined') {
                if (data !== 0  &&  data !== '0'  &&  data !== null) {
                    let file = editorActivity.file('UploadedFile', data);
                    if (file) {
                        return type === 'display' ? '<img width="64" src="' + file.webPath + '" />' : file.fileName;
                    }
                }
            }
            return 'No image';
        }
    },
    
    

    That works well too, retrieving the image information using the "editorActivity.file('UploadedFile', data)" method.

    The problem is when we open the editor for a record and trying to render all the possible options for UploadedFile images using Select2. I'm using the following code for the image field in the editor:

            {
                label: 'Image:',
                name: 'Activity._idImage',
                type: 'select2',
                fieldInfo: 'An image associated with the activity.',
                opts: {
                    initialValue: true,
                    templateResult: function formatState (state) {
                        if (!state.id) {
                            return state.text;
                        }
                        let file = editorActivity.file('UploadedFile', state.element.value);
                        let $state;
                        if (file) {
                            $state = $(
                                '<span><img src="' + file.webPath + '" class="img-flag"  /> ' + state.text + '</span>'
                            );
                        }
                        else {
                            $state = $(
                                '<span> ' + state.text + '</span>'
                            );
                        }
    
                        return $state;
            }
    
    

    state.element.value has the id of the record in the UploadedFile table that is to be shown in the Select2 field. Unfortunately, the UploadedFile.files() array only has the information for images that already appear in the Activity records. The full set of image ids is available, but I'm not clear on how to retrieve the complete image record for each image id that I want to render in the Select2 control.

    At this Select2 templateResult rendering state, how can I retrieve the full list of records for all the images in the UploadedFile table? Is this where I would use Select2's ajax method to retrieve the full list of records? Headed down that path for a bit, but ran into CSRF checking issues using the following code as part of the Select2 opts array:

                    ajax: {
                        url: '/events/edit/activities/image',
                        dataType: 'json',
                        type: 'post',
                        data: function (d) {
                            if (typeof d === 'undefined') d = {};
                            d[csrf_tokenNameKey] = csrf_tokenName;
                            d[csrf_tokenValueKey] = csrf_tokenValue;
                            d[initialValue] = true;
                            d[value] = d.Activity._idImage;
                            return d;
                    
                        },
                        processResults: function (data, params) {
                            return {
                                results: data,
                                pagination: {
                                    more: (params.page * 30) < data.total_count
                                }
                            };
                        },
                     },
    
    

    Apologies for the long post; wanted to provide as much info as possible to get some assistance.

    Am I any where close to being able to draw images in a Select2 field in the editor?

  • icefieldicefield Posts: 45Questions: 19Answers: 1

    Made a bit more progress. Using Select2's ajax method, was able to populate fields with images as desired. Here's the code that got that done from client side:

            {
                label: 'Image:',
                name: 'Activity._idImage',
                type: 'select2',
                fieldInfo: 'An image associated with the activity.',
                opts: {
                    delay: 250,
                    theme: 'bootstrap4',
                    initialValue: true,
                    ajax: {
                        url: '/events/edit/activities/image',
                        dataType: 'json',
                        type: 'post',
                        data: function (params) {
                            if (typeof params === 'undefined') params = {};
                            params[csrf_tokenNameKey] = csrf_tokenName;
                            params[csrf_tokenValueKey] = csrf_tokenValue;
                            return params;
    
                        },
                        processResults: function (data, params) {
                            return {
                                results: data
                            };
                        },
                        cache: true
                    },
                    templateResult: formatState,
                    templateSelection: formatState,
                }
    
    

    The formatState function that is called upon a search or during initial selection is:

        function formatState (state) {
            if (!state.id) {
                return state.text;
            }
            let $state = $(
                '<span><img src="' + state.webPath + '" /> ' + state.text + '</span>'
            );
    
            return $state;
        }
    
    

    The issue now is the following: When the editor form is opened, but prior to selecting the Select2 field, the 'templateSelection' function is called (in my case, formatState(...)).

    At this stage, the Select2 control is populated using initial value/label values obtained from PHP editor code I posted in the initial code block at the beginning of today's thread on this topic:

                    ->options(Options::inst()
                        ->table('UploadedFile')
                        ->value('_id')
                        ->label('fileName')
                        ->where( function ($q) {
                            $q->where( 'fileExtension', 'png', '=' );
                        })
    

    When a search is executed within the Select2 control, I'm able to return a triplet of id/text/webPath information from the server-side.

    How can I return an extra attribute (webpath) from the initial editor ->options() call (that is, the triplet value/label/webPath)?

  • allanallan Posts: 63,488Questions: 1Answers: 10,467 Site admin

    How can I return an extra attribute (webpath) from the initial editor ->options() call (that is, the triplet value/label/webPath)?

    You can't with the Options class I'm afraid, but the Editor->options() method does allow a function to be passed in so you can run your own query against the database and return whatever you need - see this part of the manual.

    You could use something like:

    ->options( function () use ($db) {
     $q = $db->query('select')
      ->table(...)
      ->...
    
      return $q->fetchAll();
    } )
    

    The full API for the database in Editor's PHP API is available here.

    Regards,
    Allan

  • icefieldicefield Posts: 45Questions: 19Answers: 1
    edited May 2020

    Ok, that gets me very close. Here's the updated ->options() snippet of code on the server-side:

                        ->options( function () use ($db) {
                            $q = $db->query('select')
                                ->table('UploadedFile')
                                ->get(['_id as value', 'fileName as label', 'webPath'])
                                ->where( function ($r) {
                                    $r->where( 'fileExtension', 'png', '=' );
                                })
                                ->exec();
                            return $q->fetchAll();
                        } )
    

    Could you give me a hint how I could reach into the array of options on the client-side editor to fetch a particular webPath value out of it? I'm not finding it straight away looking through the client debugger. Thanks very much, Allan, for your help with this.

  • icefieldicefield Posts: 45Questions: 19Answers: 1
    edited May 2020

    So, not sure if this is proper technique, but here is how I retrieved webPath on client-side:

    1) On the ajax call used to initially populate the data table, I utilized the dataSrc option to gain access to the raw data returned from the server side. I stored a copy of the array of value/label/webPath triplets I built on the server side:

            ajax: {
                url: '/events/edit/activities/edit',
                type: 'post',
                data: function (d) {
                    if (typeof d === 'undefined') d = {};
                    d[csrf_tokenNameKey] = csrf_tokenName;
                    d[csrf_tokenValueKey] = csrf_tokenValue;
                    return d;
                },
                dataSrc: function( json ) {
                    imageList = json.options['Activity._idImage'];
                    return json.data;
                }
            },
    

    imageList is declared at the file-level scope.

    2) Then, in Select2's templateResult and templateSelection's callback function, I did the following to retrieve the webPath and finally draw the image in all cases:

        function formatState (state) {
            if (!state.id) {
                return state.text;
            }
    
            let webPath = null;
            if (state.hasOwnProperty('webPath')) {
                webPath =  state.webPath;
            }
            else {
                let imageItem = imageList.find(o => o.value === state.id.toString());
                if (imageItem) webPath = imageItem.webPath;
            }
    
            let $state = $(
                '<span>' + state.text + '</span>'
            );
            if (webPath) {
                $state = $(
                    '<span><img src="' + webPath + '" class="img-flag" /> ' + state.text + '</span>'
                );
            }
    
            return $state;
        }
    
    

    Nice to learn something new every day. DataTables & Editor continues to amaze.

  • icefieldicefield Posts: 45Questions: 19Answers: 1

    To wrap this up, here are a couple of images of Select2 control (containing image and text) in the Editor.

    This one is with the dropdown closed (the default state):

    This one is with the dropdown open and a search of 'ca' occurring on the text:

  • icefieldicefield Posts: 45Questions: 19Answers: 1

    So, one last thing to note in case anyone ever tries to do something similar: because I'm using ->options(function()...) to query the list of images to show in the Select2 control, the Validate::dbValues() method no longer has access to the database/table/column information.

    So, the final version the ->options(function()...) I'm using looks like the following:

                    Field::inst('ActivityType._idImage')
                        ->options( function () use ($db) {
                            $q = $db->query('select')
                                ->table('UploadedFile')
                                ->get(['_id as value', 'fileName as label', 'webPath'])
                                ->where( function ($r) {
                                    $r->where( 'fileExtension', 'png', '=' );
                                })
                                ->exec();
                            return $q->fetchAll();
                        } )
                        ->validator(Validate::dbValues(null, '_id', 'UploadedFile', $db)),
    

    I've been few a few iterations of things to get this going, so just trying to be complete, and save others that may try something similar some time.

This discussion has been closed.