File upload not working with two Editors on same page

File upload not working with two Editors on same page

icefieldicefield Posts: 45Questions: 19Answers: 1

One of the tables used in my application has a large number of fields, including two fields used to hold references to file uploads (one an image the other a pdf). Because the number of fields is large, I'm using multiple tabs to segregate the data into reasonable size chunks. Each tab has its own editor to handle the data within that tab. Works well, but I've run into an issue when uploading image and pdf files.

One of the editors ends up referencing the incorrect set of files when attempting to open the editor. I've created a fairly easily replication of the problem starting with the Editor sample upload.html/php. To reproduce, do the following:

Add another column to the users table as pdf int default NULL, and recreate the sample database.

Copy upload.html to uploadTest.html and modify contents as follows:

<snip><snip><snip>

var editor1; // use a global for the submit and return data rendering in the examples
var editor2; // use a global for the submit and return data rendering in the examples

$(document).ready(function() {
    editor1 = new $.fn.dataTable.Editor( {
        ajax: "../../controllers/uploadTestImage.php",
        table: "#editor1",
        fields: [ {
                label: "Last name:",
                name: "last_name"
            }, {
                label: "Image:",
                name: "image",
                type: "upload",
                display: function ( file_id ) {
                    return '<img src="'+editor1.file( 'files', file_id ).web_path+'"/>';
                },
                clearText: "Clear",
                noImageText: 'No image'
            }
        ]
    } );

    var table = $('#editor1').DataTable( {
        dom: "Bfrtip",
        ajax: "../../controllers/uploadTestImage.php",
        columns: [
            { data: "last_name" },
            {
                data: "image",
                render: function ( file_id ) {
                    return file_id ?
                        '<img src="'+editor1.file( 'files', file_id ).web_path+'"/>' :
                        null;
                },
                defaultContent: "No image",
                title: "Image"
            }
        ],
        select: true,
        buttons: [
            { extend: "create", editor: editor1 },
            { extend: "edit",   editor: editor1 },
            { extend: "remove", editor: editor1 }
        ]
    } );

    editor2 = new $.fn.dataTable.Editor( {
        ajax: "../../controllers/uploadTestPDF.php",
        table: "#editor2",
        fields: [ {
            label: "Last name:",
            name: "last_name"
        }, {
            label: "PDF:",
            name: "pdf",
            type: "upload",
            display: function ( file_id ) {
                return '<img src="'+editor2.file( 'files', file_id ).web_path+'"/>';
            },
            clearText: "Clear",
            noImageText: 'No image'
        }
        ]
    } );

    var table = $('#editor2').DataTable( {
        dom: "Bfrtip",
        ajax: "../../controllers/uploadTestPDF.php",
        columns: [
            { data: "last_name" },
            {
                data: "pdf",
                render: function ( file_id ) {
                    return file_id ?
                        '<img src="'+editor2.file( 'files', file_id ).web_path+'"/>' :
                        null;
                },
                defaultContent: "No PDF",
                title: "PDF"
            }
        ],
        select: true,
        buttons: [
            { extend: "create", editor: editor2 },
            { extend: "edit",   editor: editor2 },
            { extend: "remove", editor: editor2 }
        ]
    } );

} );



    </script>
</head>
<body class="dt-example php">
    <div class="container">
        <section>
            <h1>Editor example <span>File upload</span></h1>
            <div class="info">
                <p>This example shows Editor being used with the <a href="//editor.datatables.net/reference/field/upload"><code class="field" title=
                "Editor field type">upload</code></a> fields type to give end users the ability to upload a file in the form. The <a href=
                "//editor.datatables.net/reference/field/upload"><code class="field" title="Editor field type">upload</code></a> field type allows just a single file to be
                uploaded, while its companion input type <a href="//editor.datatables.net/reference/field/uploadMany"><code class="field" title=
                "Editor field type">uploadMany</code></a> provides the ability to have multiple files uploaded for a single field.</p>
                <p>The upload options of Editor are extensively documented in the manual (<a href="//editor.datatables.net/manual/upload">Javascript</a>, <a href=
                "//editor.datatables.net/manual/php/upload">PHP</a>, <a href="//editor.datatables.net/manual/net/upload">.NET</a> and <a href=
                "//editor.datatables.net/manual/node/upload">NodeJS</a>) and details the various options available.</p>
                <p>In this example an image file can be uploaded, limited to 500KB using server-side validation. To display the image a simple <code class="tag" title=
                "HTML tag">img</code> tag is used, with information about the file to be displayed retrieved using the <a href=
                "//editor.datatables.net/reference/api/file()"><code class="api" title="Editor API method">file()</code></a> method which Editor makes available and is
                automatically populated based on the server-side configuration.</p>
            </div>
            <div class="demo-html"></div>
            <table id="editor1" class="display" cellspacing="0" width="100%">
                <thead>
                    <tr>
                        <th>Last name</th>
                        <th>Image</th>
                    </tr>
                </thead>
                <tfoot>
                    <tr>
                        <th>Last name</th>
                        <th>Image</th>
                    </tr>
                </tfoot>
            </table>
            <table id="editor2" class="display" cellspacing="0" width="100%">
                <thead>
                <tr>
                    <th>Last name</th>
                    <th>PDF</th>
                </tr>
                </thead>
                <tfoot>
                <tr>
                    <th>PDF</th>
                    <th>Image</th>
                </tr>
                </tfoot>
            </table>
            <ul class="tabs">
                <li class="active">Javascript</li>
                <li>HTML</li>
                <li>CSS</li>
                <li>Ajax</li>
                <li>Server-side script</li>
            </ul>
            <div class="tabs">

<snip><snip><snip>

Then, copy upload.php to uploadTestImage.php and change as follows:

<?php

/*
 * Example PHP implementation used for the index.html example
 */

// DataTables PHP library
include( "../lib/DataTables.php" );

// Alias Editor classes so they are easy to use
use
    DataTables\Editor,
    DataTables\Editor\Field,
    DataTables\Editor\Format,
    DataTables\Editor\Mjoin,
    DataTables\Editor\Options,
    DataTables\Editor\Upload,
    DataTables\Editor\Validate,
    DataTables\Editor\ValidateOptions;


// Build our Editor instance and process the data coming from _POST
Editor::inst( $db, 'users' )
    ->fields(
        Field::inst( 'last_name' ),
        Field::inst( 'image' )
            ->setFormatter( Format::ifEmpty( null ) )
            ->upload( Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__.__EXTN__' )
                ->db( 'files', 'id', array(
                    'filename'    => Upload::DB_FILE_NAME,
                    'filesize'    => Upload::DB_FILE_SIZE,
                    'web_path'    => Upload::DB_WEB_PATH,
                    'system_path' => Upload::DB_SYSTEM_PATH
                ) )
                ->validator( Validate::fileSize( 500000, 'Files must be smaller that 500K' ) )
                ->validator( Validate::fileExtensions( array( 'png', 'jpg', 'jpeg', 'gif' ), "Please upload an image" ) )
            )
    )
    ->process( $_POST )
    ->json();

Similarly, copy upload.php to uploadTestPDF.php and changes a follows:

<?php

/*
 * Example PHP implementation used for the index.html example
 */

// DataTables PHP library
include( "../lib/DataTables.php" );

// Alias Editor classes so they are easy to use
use
    DataTables\Editor,
    DataTables\Editor\Field,
    DataTables\Editor\Format,
    DataTables\Editor\Mjoin,
    DataTables\Editor\Options,
    DataTables\Editor\Upload,
    DataTables\Editor\Validate,
    DataTables\Editor\ValidateOptions;


// Build our Editor instance and process the data coming from _POST
Editor::inst( $db, 'users' )
    ->fields(
        Field::inst( 'last_name' ),
        Field::inst( 'pdf' )
            ->setFormatter( Format::ifEmpty( null ) )
            ->upload( Upload::inst( $_SERVER['DOCUMENT_ROOT'].'/uploads/__ID__.__EXTN__' )
                ->db( 'files', 'id', array(
                    'filename'    => Upload::DB_FILE_NAME,
                    'filesize'    => Upload::DB_FILE_SIZE,
                    'web_path'    => Upload::DB_WEB_PATH,
                    'system_path' => Upload::DB_SYSTEM_PATH
                ) )
                ->validator( Validate::fileSize( 500000, 'Files must be smaller that 500K' ) )
                ->validator( Validate::fileExtensions( array( 'pdf' ), "Please upload a PDF file" ) )
            )
    )
    ->process( $_POST )
    ->json();

Now you're ready to test. Load the uploadTest.html and you should see two tables, each with two columns. First table has Last Name and Image columns, second table has Last Name and PDF columns. All good to this point.

Next, select first record in first table, then Edit first table, and upload any image. This is good.

Then, select second record in second table, then Edit second table, and upload any PDF. This works too.

Next, refresh the page. Image and PDF are there. That's good.

Now select the first record in first table, and then Edit first table. Doesn't work. Edit button just shows spinning cursor. I poked around down into DataTables.editor.min.js:105 -- and wrong array of files seems to be there (array from second editor, not first one).

If you refresh the page, select the second record in the second table, and then Edit, everything works okay.

Somewhere along the way, the list of files used for each editor is getting out of synch.

Note, I am using DataTables 1.10.18 and Editor 1.8.1 for both my own application, and for the example that I reproduced above.

Also, I did make the change you outlined in this fix from this issue to editor.php, but the problem still exists for me.

This question has accepted answers - jump to:

Answers

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin

    Thanks for this. I'll need to setup my test environment for this and get back to you. Hopefully tomorrow, possibly Friday.

    Allan

  • icefieldicefield Posts: 45Questions: 19Answers: 1

    Bumping this one back up. Is a fix or workaround available?

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin
    Answer ✓

    Apologies for the massive delay in me picking this up - but that you for the support purchase to prompt me! I believe the error is in how Editor is assigning the file information when the page is first loaded - specifically it does: Editor.files[ name ] = files; - i.e. a straight write, rather than attempting a merge. So this issue appears as of 1.8 since Editor is attempting to limit the files it reads information about from the database to only those in the current table. Since you've got two reading the same table, they need to be merged, otherwise the first will always be overwritten.

    To fix - in the Editor source find:

    $(document).on( 'xhr.dt', function (e, ctx, json) {
        if ( e.namespace !== 'dt' ) {
            return;
        }
    
        if ( json && json.files ) {
            $.each( json.files, function ( name, files ) {
                Editor.files[ name ] = files;
            } );
        }
    } );
    

    And replace with:

    $(document).on( 'xhr.dt', function (e, ctx, json) {
        if ( e.namespace !== 'dt' ) {
            return;
        }
    
        if ( json && json.files ) {
            $.each( json.files, function ( name, files ) {
                if ( !Editor.files[ name ] ) {
                    Editor.files[ name ] = {};
                }
    
                $.extend( Editor.files[ name ], files );
            } );
        }
    } );
    

    Its only the inside of the each method that is changed, but I've included the whole xhr listener just for reference. The key here is that now a merge will be done on the file information rather than just overwriting the data!

    Let me know how you get on with it.

    Regards,
    Allan

  • icefieldicefield Posts: 45Questions: 19Answers: 1
    edited December 2018

    Hi Alan,

    Applying the modifications you define above does resolve the issue. Thank you for this.

    Could you confirm that this change will be in an upcoming release of the DataTables Editor?

    Thanks again.

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin
    Answer ✓

    Yes indeed - that will be in the next patch release of Editor.

    Many thanks for confirming that works for your use case.

    Regards,
    Allan

  • mommom Posts: 14Questions: 4Answers: 0

    Hi Allan,

    I've hit another issue related to multiple file references - similar to the one reported by icefield but in a different aspect. I have one table, one instance of editor, but the table contains two fields which are references to files. In short - the json returned from the editor-php backend script returns the files from the first field only.

    I confirmed that an older project, built with Editor 1.7.3, in a similar case returns all rows from the files DB table (not only the ones matching the search query). With Editor 1.8.1 it tries to return only the relevant files, but is omitting the case of two Upload::inst-ances.

    The environment could be briefly demonstrated like this:

    DB table `products`:
    -------------------------        
    | id | name | image1 | image2 |
    -------------------------        
    | 1 | Name1 | 1 | 2 |
    | 2 | Name2 | 3 | 4 |
    -------------------------
    
    table `files`:
    ----------------------------------------------
    | id | filename | filesize | filepath |
    ----------------------------------------------
    | 1 | some-file.png | 2941 | /product_images/1.png |
    | 2 | another-file.png | 27628 | /product_images/2.png |
    | 3 | untitled.jpg | 31480 | /product_images/3.jpg |
    | 4 | pr2.png | 5039 | /product_images/4.png |
    ----------------------------------------------
    
    Backend script:
    $iu_file_pattern = $_SERVER['DOCUMENT_ROOT'].'/product_images/__ID__.__EXTN__';
    Editor::inst( $db, 'products' )
    ->fields(
        Field::inst( 'id,' )
        Field::inst( 'name' )
        Field::inst( 'image1' )
            ->upload( Upload::inst( $iu_file_pattern )
                ->db( 'files', 'id', ['filename' => Upload::DB_FILE_NAME, 'filesize' => Upload::DB_FILE_SIZE, 'filepath' => Upload::DB_WEB_PATH ] )
            )
            ->setFormatter( 'Format::nullEmpty' ),        
        Field::inst( 'image2' )
            ->upload( Upload::inst( $iu_file_pattern )
                ->db( 'files', 'id', ['filename' => Upload::DB_FILE_NAME, 'filesize' => Upload::DB_FILE_SIZE, 'filepath' => Upload::DB_WEB_PATH ] )
            )
            ->setFormatter( 'Format::nullEmpty' )
    )
    ->process( $_POST )
    ->json();        
    

    The returned JSON data contains:

    "files":{
        "files":{
            "1": {"id":"1","filename":"some-file.png","filesize":"2941","filepath":"\/product_images\/1.png"},
            "2": {"id":"2","filename":"another-file.png","filesize":"27628","filepath":"\/product_images\/2.png"}
        }
    }
    

    As a result, when I try to edit product 2, an error is thrown at
    editor.file( 'files', file_id ).filename > Uncaught Unknown file id 3 in table files
    and the editor modal does not show up at all.

    It would be great if you could advise what piece of code to modify and where in the php libraries so that JSON output contains merged info about all upload instances.

    Thanks!

  • Restful Web ServicesRestful Web Services Posts: 202Questions: 49Answers: 2
    edited February 2019

    I am experience this exact same issue as identified by mom. Does a a workable solution at this time?

  • allanallan Posts: 63,498Questions: 1Answers: 10,471 Site admin
    Answer ✓

    Yes, use the latest version of the PHP files from the repo. The 1.8.2 release will carry the fix.

    Allan

This discussion has been closed.