Changeset 1399


Ignore:
Timestamp:
2009-12-11 16:55:18 (2 years ago)
Author:
mpo
Message:

serious refactoring and cleanup of the upload-control

  • there was no need to be composite
  • setWireValue is not the right one to be overriding > set/getUserValue is better
  • implementing nicer human format of the size
  • preparing to solve (not yet tested though) bugs #210, #211, #221, #240

stuff that still needs to happen:

  • introducing heartbeat (since temp service will auto remove files not accessed in 5')
  • actually calling delete on neglected resources (ajax call is not ok yet)
  • setWireValue should allow for different location on already stored files
  • tests on ie7 (#222) and chrome
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/modules/kauri-forms/kauri-forms-framework/src/main/kauri/static-{build}.key/kauri.forms/upload.js

    r1296 r1399  
    1414     
    1515    kf.fieldTypes.put("file", { 
    16         base: "composite", 
    17         members: { 
    18             "filelocation": { 
    19                 type: "string" 
    20             }, 
    21             "mimetype": { 
    22                 type: "string" 
    23             }, 
    24             "size": { 
    25                 type: "integer" 
    26             }, 
    27             "filename": { 
    28                 type: "string" 
    29             } 
    30         }, 
     16        base: "string", 
    3117        control: "upload-control" 
    3218    }); 
    3319     
    34     $.inherit(UploadControl, kf.AbstractContainerControl); 
     20    $.inherit(UploadControl, kf.Control); 
    3521     
    3622    /** 
     
    4935     * Uri used for uploading the file to immediately after setting choosing the local file 
    5036     */ 
    51     UploadControl.prototype.uploadUri = "/tmp/?accept=text/plain"; 
     37    UploadControl.prototype.uploadUri = "/kauri/upload"; 
    5238     
    5339    UploadControl.prototype.elements = {}; 
    5440    $.extend(UploadControl.prototype.elements, kf.AbstractContainerControl.prototype.elements); 
    5541    $.extend(UploadControl.prototype.elements, { 
    56         "container": { 
    57             create: "<div/>" 
    58         }, 
    59         "layout": { 
     42        "input": { 
    6043            create: "<div/>" 
    6144        } 
    6245    }); 
    6346     
     47 
     48    var INIT_FRAME_URL = "about:blank"; 
     49 
    6450    /** 
    6551     * Initializes member types (filename, filelocation, size & mimetype) 
     
    6854     */ 
    6955    UploadControl.prototype.initElements = function(create){ 
    70         var container = this.getElement("container"); 
    71         container.hide(); 
    72          
    73         this._children = {}; 
    74          
    75         var type = this.getType(); 
    76         var memberTypes = type.getMemberTypes(); 
    77          
    78         for (var memberName in memberTypes) { 
    79             var memberType = memberTypes[memberName]; 
    80             var childControl = this.createChildControl(memberName, memberType); 
    81             this.putChild(memberName, childControl); 
    82         } 
    83          
    84         this._createUploadInput(); 
    85          
    86         var tmpFormContainer = $("<div/>").insertAfter(this._form.getRootElement()); 
    87         tmpFormContainer.hide(); 
    88          
    89         var uploadFrameId = this.getId() + "iFrame"; 
    90          
    91         // Create a fake form with target a hidden iframe, the form will be submitted the minute a file is chosen. When uploaded the iframe 
    92         // should contain the response from the upload 
    93         this.uploadForm = $("<form method='POST' enctype='multipart/form-data' action='" + this.uploadUri + "' target='" + uploadFrameId + "' />").appendTo(tmpFormContainer); 
    94         this.uploadIframe = $("<iframe src='about:blank' id='" + uploadFrameId + "' name='" + uploadFrameId + "'/>").appendTo(this.uploadForm); 
    95          
    96         // Used for showing loading messages and file information 
    97         this.uploadView = $("<div/>").insertAfter(container); 
     56 
     57        var me = this; 
     58        var $container = this.getElement("input"); 
     59         
     60        // a hidden container for the hidden form 
     61        var hiddenFormContainer =  $("<div/>").insertAfter(this._form.getRootElement()).hide(); 
     62        // and a unique id to use  
     63        var uploadFrameId =( "iFrame" + this.getAbsoluteId() ).replace(/\//g,"_"); 
     64         
     65 
     66        // a hidden frame to send the upload-response to 
     67        this.uploadIframe = $("<iframe src='" + INIT_FRAME_URL + "' id='" + uploadFrameId + "' name='" + uploadFrameId + "'/>").hide().appendTo($container); 
     68        this.uploadIframe.unbind().load(function(){ 
     69            me.handleUploadResponse(this.contentWindow.document); 
     70        }); 
     71 
     72         
     73        // A hidden form targetted at the hidden iframe. 
     74        // This crafted form will be submitted the minute a file is chosen. It is distinct from the kauri-form holding this control.  
     75        // When uploaded the iframe should contain the response from the upload 
     76        this.uploadForm = $("<form method='POST' enctype='multipart/form-data' action='" + this.uploadUri + "?accept=text/plain' target='" + uploadFrameId + "' />").appendTo(hiddenFormContainer); 
     77         
     78        // an area for showing upload progress and status. 
     79        this.uploadView = $("<div/>").appendTo($container); 
    9880    }; 
    9981     
    100     UploadControl.prototype.initEventWiring = function(){ 
    101         var me = this; 
    102         this.uploadIframe.unbind().load(function(){ 
    103             // lets assume for now that it's always json 
    104             var idoc = this.contentWindow.document; 
     82 
     83    UploadControl.prototype.newUploadInput = function(me, $container) { 
     84        me = me || this; 
     85        $container = $container || this.getElement("input"); 
     86         
     87        if (this.uploadInput) 
     88            this.uploadInput.remove(); 
     89             
     90        // the visual input control the end user will see 
     91        this.uploadInput = $("<input type='file' name='file'/>").appendTo($container);         
     92        this.uploadInput.change(function(evt){ 
     93            me.performUpload(); 
     94        }); 
     95    } 
     96     
     97    UploadControl.prototype.performUpload = function() { 
     98        // remove any older input fields in the upload-form 
     99        $("input", this.uploadForm).remove(); 
     100         
     101        // clone the user-controlled file input and place the clone in the upload-form         
     102        var activeInput = this.uploadInput.clone().appendTo(this.uploadForm); 
     103         
     104        // replace the visible input control with a loading msg 
     105        this.uploadInput.remove(); 
     106        this.uploadView.html("<div class='upload-loading'/>"); 
     107         
     108        // start upload by submitting the upload-form 
     109        this.uploadForm.submit(); 
     110    } 
     111     
     112    UploadControl.prototype.handleUploadResponse = function(idoc) { 
     113        if (!idoc || INIT_FRAME_URL == idoc.URL) 
     114            return; // no data to be read, this is just starting up 
     115             
     116        try { 
    105117            var ibody = $("body", idoc); 
    106118            var responseText = ibody.text(); 
     
    109121                var response = kp.JSON.parse(responseText); 
    110122                if (response.length > 0) { 
    111                     var pos = me.uploadUri.indexOf("?") > -1 ? me.uploadUri.lastIndexOf("?") : me.uploadUri.length; 
    112                     var baseurl = me.uploadUri.substring(0, pos); 
    113123                     
    114                     var value = { 
    115                         filelocation: baseurl + response[0].id, 
    116                         filename: response[0].filename, 
    117                         size: response[0].size, 
    118                         mimetype: response[0].type 
    119                     }; 
    120                     me.setWireValue(value); 
     124                    var value = response[0]; //this control only uploads one file at a time. 
     125                    this.setWireValue(value); 
    121126                } 
    122                 // after loading remove the cloned file input 
    123                 $("input", me.uploadForm).remove(); 
    124127            } 
     128        } catch (e) { 
     129            // if loading failed: show some bad message 
     130            this.uploadView.html("<span class='message'>Upload Failed.</span>"); 
     131             
     132            // even if loading failed the control should go back to some decent view-state 
     133            this.updateView(); 
     134        } 
     135    } 
     136     
     137    UploadControl.prototype.dataRef = function(id){ 
     138        var uri = this.uploadUri; 
     139        var pos = uri.indexOf("?") > -1 ? uri.lastIndexOf("?") : uri.length; 
     140        var base = uri.substring(0, pos); 
     141         
     142        return base + id; 
     143    } 
     144     
     145    var ORDER_SIZES = [" Bytes", " KB", " MB"]; //higher order seems unlikely for uploads 
     146    UploadControl.prototype.humanSize = function(size, level) { 
     147        level = level || 0; 
     148        var nextOrder = Number(new Number(size) / 1024).toFixed(); 
     149        if (nextOrder != "0" && ORDER_SIZES.length > level +1) 
     150            return this.humanSize(nextOrder, level+1); 
     151        //else  
     152        return size + ORDER_SIZES[level]; 
     153    } 
     154     
     155    UploadControl.prototype.updateView = function () { 
     156        var me = this; 
     157        var value = this.getValue(); 
     158         
     159         
     160        if( value && value.id) { // there is data to show... 
     161            this.uploadView.empty(); 
     162 
     163            // remove the input and replace with file information 
     164            this.uploadInput.remove(); 
     165             
     166            // display file information 
     167            var size = value.size ?  this.humanSize(value.size) : "N/A"; 
     168            var ref = this.dataRef(value.id); 
     169            var name = value.filename || "unnamed document"; 
     170            var mimetype = value.mimetype || "application/octet-stream"; 
     171             
     172            this.uploadView.html("<a target='_blank' href='" + ref + "'>" + name + "</a><span class='upload-info'> (type: " + mimetype + ", size: " + size + " ) </span> "); 
     173            var rmBtn = $("<a href='javascript:void(0)'>remove</a>").appendTo(this.uploadView); 
     174            rmBtn.click(function(evt){ 
     175                me.clearUploadedFile(value.id); 
     176            }); 
     177             
     178            this.startHeartBeat(); 
     179        } else { 
     180            this.uploadView.html(""); 
     181            this.newUploadInput(me); 
     182        } 
     183    } 
     184     
     185    UploadControl.prototype.clearUploadedFile = function (key) { 
     186         
     187        //stop keeping it life 
     188        this.endHeartBeat(key); 
     189        //then kill it off using a call to delete via AJAX 
     190        $.ajax({ 
     191            uri: this.dataRef(key) +"?method=DELETE", 
     192            method: "POST" 
    125193        }); 
    126     }; 
    127      
    128     /** 
    129      * Create a file input with an onChange handler that submits the file to the server 
    130      */ 
    131     UploadControl.prototype._createUploadInput = function(){ 
    132         var container = this.getElement("container"); 
    133         this.uploadInput = $("<input type='file'/>").insertAfter(container); 
    134         var me = this; 
    135          
    136         this.uploadInput.change(function(evt){ 
    137  
    138             // clone the file input and put place the clone in the hidden form (since we want to fake an ajax upload) 
    139             var fileInput = me.uploadInput.clone().insertAfter(me.uploadIframe); 
    140             fileInput.attr("name", "file"); 
    141             // replace the input with a loading msg 
    142             me.uploadInput.remove(); 
    143             $("<div class='upload-loading'/>").appendTo(me.uploadView); 
    144             me.uploadForm.submit(); 
    145         }); 
    146     } 
    147      
    148     /** 
    149     * Sets the values for all of this controls children. The value is expected to be in wire format     
    150     * Also displays the file information 
    151     * @param {Array} value The values should be ordered in the same order as the children     
    152     */ 
    153     UploadControl.prototype.setWireValue = function (value) { 
    154         var me = this; 
    155         var container = this.uploadView; 
    156         container.empty(); 
    157          
    158         if(value){ 
    159              
    160             // remove the input and replace with file information 
    161             me.uploadInput.remove(); 
    162              
    163             // display file information 
    164             var kb = Number(new Number(value.size) / 1024).toFixed() + "KB"; 
    165             $("<a target='_blank' href='" + value.filelocation + "'>" + value.filename + "</a><span class='upload-info'>( " + value.mimetype + ", " + kb + " )</span>").appendTo(container); 
    166             var rmBtn = $("<a href='javascript:void(0)'>remove</a>").appendTo(container); 
    167             rmBtn.click(function(evt){ 
    168                 container.empty(); 
    169                 //TODO remove tmp resource 
    170                 me._createUploadInput(); 
    171              
    172             }); 
    173         } 
    174          
    175         // set the child controls 
    176         return this.setValueLoop("setWireValue", value); 
    177     } 
    178      
     194         
     195        this.setValue(undefined); 
     196    } 
     197     
     198    UploadControl.prototype.startHeartBeat = function (key) { 
     199        //TODO 
     200    } 
     201     
     202    UploadControl.prototype.endHeartBeat = function (key) { 
     203        //TODO 
     204    } 
     205         
     206    UploadControl.prototype.setUserValue = function (value) { 
     207        // no such thing, we just use the event to update the visual 
     208        this.updateView(); 
     209    } 
     210     
     211    UploadControl.prototype.getUserValue = function () { 
     212        // no such thing 
     213    } 
     214 
    179215    kf.controlTypes.put("upload-control", UploadControl); 
    180216     
Note: See TracChangeset for help on using the changeset viewer.