Source: 04-CUploadColumn.jsx

/**
 * Upload column.
 *
 * This column for uploading files.
 *
 * @arg {string} this.props.upload_endpoint - Endpoint for uploading files.
 * @arg {undefined | Boolean} this.props.multiple - Multiple upload allowed. Not tested.
 * @arg {undefined | string} this.props.links_endpoint - Link endpoint. No link available if undefined.
 * @arg {undefined | int} this.props.filename_len - Max length for filename. 12 if undefined.
 * @arg {undefined | int} this.props.max_file_size - Maximum file size.
 * @arg {undefined | string[]} this.props.allowed_extentions - Allowed file extention.
 */

class CUploadColumn extends CTableColumn{
    constructor() {
        super();

        this.filterChanged = this.filterChanged.bind(this);
        this.editorCleared = this.editorCleared.bind(this);
        this.editorChanged = this.editorChanged.bind(this);

        this.state = {fileinfo: {uploaded:false, count:0, filelabel:[], uid:[], filelink:[], filedate:[]}};
        this.ref = createRef();
    }

    file_name_shortifier(fname){
        var length = 12;
        if(this.props.filename_len){
            length = this.props.filename_len;
        }
        if(fname.length < length) return fname;
        return fname.substring(0,length-5) + '...' + fname.substring(fname.length-5);
    }

    value_parser(value){
        if (value == '' || value == null){
            return {uploaded:false, count:0, filelabel:[], uid:[], filelink:[], filedate:[]};
        }

        var links = null;
        if(this.props.links_endpoint){
            links = this.props.links_endpoint;
        }

        var flines = value.split(';').filter(function(el) {return el.length != 0});

        if(flines.length == 1 ){
            var fcomp = flines[0].split(':');
            return {uploaded:true, count:1, filelabel:[this.file_name_shortifier(fcomp[1])], uid:[fcomp[0]], filelink:[(links ? links+fcomp[0] : '')]};
        }

        var uids = [];
        var labels = [];
        var links = [];

        flines.forEach(function(frecord){
            var fcomp = frecord.split(':').filter(function(el) {return el.length != 0});
            uids.push(fcomp[0]);
            labels.push(this.file_name_shortifier(fcomp[1]));
            links.push(links ? links+fcomp[0] : '');
        });

        return {uploaded:true, count:flines.length, filelabel:labels, uid:uids, filelink:links};
    }


    componentDidMount() {
        this.setState({fileinfo: this.value_parser(this.value())});
    }

    render_cell() {

         var fileinfo = this.value_parser(this.value());

        if (!fileinfo.uploaded){
            return <a class="button is-info is-outlined" disabled><span class="material-icons">attach_file</span> {this.props.table.props.lang.no_file}</a>;
        }

        if (fileinfo.count == 1){
            var filelink = {};
            if (this.props.links_endpoint) {
                filelink = {"href":fileinfo.filelink, "target": "_blank", "disabled": false};
            }
            return h("a",{"class": "button is-info is-outlined", "disabled": true, ...filelink}, <><span class="material-icons">attach_file</span>{fileinfo.filelabel[0]}</>);
        } else {
            return <a class="button is-info is-outlined" disabled><span class="material-icons">attach_file</span> {this.props.table.props.lang.multiple_files} {fileinfo.count}</a>;
        }
    }

    filterChanged(e){
        if(e.target.value == ''){
            this.props.table.change_filter_for_column(this.props.column, null);
        } else {
            this.props.table.change_filter_for_column(this.props.column, e.target.value);
        }
    }

    render_search() {
        if (typeof this.props.filtering === 'undefined') {
            // do nothing
        } else {
            return <div class="select">
                     <select onChange={this.filterChanged} value={this.props.filtering}>
                       <option value=''>{this.props.table.props.lang.no_filter}</option>
                       <option value='%nodata'>{this.props.table.props.lang.file_filter_no}</option>
                       <option value='%notempty'>{this.props.table.props.lang.file_filter_yes}</option>
                     </select>
                   </div>;
        }
    }

    editorCleared(e) {
        this.setState({fileinfo: this.value_parser('')});
        this.props.table.notify_changes(this.props.row, this.props.column, '');
    }

    editorChanged(e) {

        var form_data = new FormData();

        if(e.target.files.length > 1 && (!this.props.multiple)){
            alert(this.props.table.props.lang.file_only_one);
            return;
        }

        for(var file_index = 0; file_index < e.target.files.length; file_index++){
            if(this.props.max_file_size){
                if(e.target.files[file_index].size > this.props.max_file_size){
                    alert(this.props.table.props.lang.file_to_large);
                    return;
                }
            }
            if(this.props.allowed_extentions){
                if (this.props.allowed_extentions.filter(item => e.target.files[file_index].name.toLowerCase().endsWith(item)).length == 0){
                    alert(this.props.table.props.lang.file_wrong_extention);
                    return;
                }
            }

            form_data.append("file"+file_index, e.target.files[file_index]);
        }

        var self = this;

        this.props.table.setState({waiting_active: true});

        fetch(this.props.upload_endpoint, {method: 'POST', body: form_data})
        .then(function(response){
            self.props.table.setState({waiting_active: false}); // always disable table waiting
            if (response.ok) {return response.json();}
            else {alert(self.props.table.props.lang.server_error + response.status)} })
        .then(function (result) {
            if (!result) return;
            if(result.Result == 'OK'){
               self.setState({fileinfo: self.value_parser(result.Files)});
               self.props.table.notify_changes(self.props.row, self.props.column, result.Files);
            }});
    }

    render_editor() {
        if (!this.state.fileinfo.uploaded){
            return <><label class="label">{this.title()}</label>
                   <div class="field has-addons">
                       <div class="control">
                           <button class="button is-info" disabled><span class="material-icons">attach_file</span> {this.props.table.props.lang.no_file}</button>
                        </div>
                        <div class="control">
                            <button class="button is-info is-danger" disabled><span class="material-icons">delete</span></button>
                        </div>
                        <div class="control">
                            <button class="button is-info"><span class="material-icons">upload</span></button>
                            <input class="file-input" type="file" name="file" multiple={this.props.multiple ? "true" : "false"} onChange={this.editorChanged}/>
                        </div>
                    </div>
                    {this.props.footnote ? <div class="help">{this.props.footnote}</div> : ''}
                    </>;
        }

        if (this.state.fileinfo.count == 1){
            var filelink = {};
            if (this.props.links_endpoint) {
                filelink = {"href":this.state.fileinfo.filelink, "target": "_blank"};
            }
            return <><label class="label">{this.title()}</label>
                   <div class="field has-addons">
                       <div class="control">
                           {h("a",{"class": "button is-info", ...filelink}, <><span class="material-icons">attach_file</span> {this.state.fileinfo.filelabel[0]}</>)}
                        </div>
                        <div class="control">
                            <button class="button is-info is-danger" onClick={this.editorCleared}><span class="material-icons">delete</span></button>
                        </div>
                        <div class="control">
                            <button class="button is-info"><span class="material-icons">upload</span></button>
                            <input class="file-input" type="file" name="file" multiple={this.props.multiple ? "true" : "false"} onChange={this.editorChanged}/>
                        </div>
                    </div>
                    {this.props.footnote ? <div class="help">{this.props.footnote}</div> : ''}
                    </>;
        } else {
            return <><label class="label">{this.title()}</label>
                   <div class="field has-addons">
                       <div class="control">
                           {h("a",{"class": "button is-info", "disabled": true}, <><span class="file-icon">⊖</span>{this.props.table.props.lang.multiple_files} {this.state.fileinfo.count}</>)}
                        </div>
                        <div class="control">
                            <button class="button is-info is-danger" onClick={this.editorCleared}><span class="material-icons">cancel</span></button>
                        </div>
                        <div class="control">
                            <button class="button is-info">↥</button>
                            <input class="file-input" type="file" name="file" multiple={this.props.multiple ? "true" : "false"} onChange={this.editorChanged}/>
                        </div>
                    </div>
                    {this.props.footnote ? <div class="help">{this.props.footnote}</div> : ''}
                    </>;
        }
    }
}