2011-09-01 01:37:30 +02:00
/ * *
2013-04-13 21:00:13 +02:00
* EGroupware eTemplate2 - JS Number object
2011-09-01 01:37:30 +02:00
*
* @ license http : //opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @ package etemplate
* @ subpackage api
* @ link http : //www.egroupware.org
* @ author Nathan Gray
* @ copyright Nathan Gray 2011
* @ version $Id$
* /
"use strict" ;
/ * e g w : u s e s
et2 _core _inputWidget ;
2011-09-02 00:07:30 +02:00
phpgwapi . jquery . jquery . html5 _upload ;
2011-09-01 01:37:30 +02:00
* /
/ * *
* Class which implements file upload
2013-04-13 21:00:13 +02:00
*
* @ augments et2 _inputWidget
2011-09-01 01:37:30 +02:00
* /
2013-04-13 21:00:13 +02:00
var et2 _file = et2 _inputWidget . extend (
{
2011-09-01 01:37:30 +02:00
attributes : {
"multiple" : {
"name" : "Multiple files" ,
"type" : "boolean" ,
"default" : false ,
"description" : "Allow the user to select more than one file to upload at a time. Subject to browser support."
} ,
"max_file_size" : {
"name" : "Maximum file size" ,
"type" : "integer" ,
"default" : "8388608" ,
"description" : "Largest file accepted, in bytes. Subject to server limitations. 8Mb = 8388608"
} ,
2012-03-30 00:45:58 +02:00
"mime" : {
"name" : "Allowed file types" ,
"type" : "string" ,
"default" : et2 _no _init ,
"description" : "Mime type (eg: image/png) or regex (eg: /^text\//i) for allowed file types"
} ,
2011-09-01 01:37:30 +02:00
"blur" : {
2013-04-13 21:00:13 +02:00
"name" : "Placeholder" ,
"type" : "string" ,
"default" : "" ,
"description" : "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text."
2011-09-01 01:37:30 +02:00
} ,
"progress" : {
"name" : "Progress node" ,
"type" : "string" ,
"default" : et2 _no _init ,
2013-09-10 14:57:40 +02:00
"description" : "The ID of an alternate node (div) to display progress and results. The Node is fetched with et2 getWidgetById so you MUST use the id assigned in XET-File (it may not be available at creation time, so we (re)check on createStatus time)"
2011-09-09 02:05:18 +02:00
} ,
"onStart" : {
"name" : "Start event handler" ,
"type" : "any" ,
"default" : et2 _no _init ,
"description" : "A (js) function called when an upload starts. Return true to continue with upload, false to cancel."
} ,
"onFinish" : {
"name" : "Finish event handler" ,
"type" : "any" ,
"default" : et2 _no _init ,
"description" : "A (js) function called when all files to be uploaded are finished."
2013-08-03 21:12:38 +02:00
} ,
drop _target : {
"name" : "Optional, additional drop target for HTML5 uploads" ,
"type" : "string" ,
"default" : et2 _no _init ,
"description" : "The ID of an additional drop target for HTML5 drag-n-drop file uploads"
2011-09-01 01:37:30 +02:00
}
} ,
2011-09-02 00:07:30 +02:00
asyncOptions : { } ,
2013-04-13 21:00:13 +02:00
/ * *
* Constructor
*
* @ memberOf et2 _file
* /
2011-09-01 01:37:30 +02:00
init : function ( ) {
2013-04-13 21:00:13 +02:00
this . _super . apply ( this , arguments ) ;
2011-09-01 01:37:30 +02:00
this . node = null ;
2011-09-08 22:33:46 +02:00
this . input = null ;
this . progress = null ;
if ( ! this . options . id ) {
console . warn ( "File widget needs an ID. Used 'file_widget'." ) ;
this . options . id = "file_widget" ;
}
2011-09-01 01:37:30 +02:00
2012-03-30 00:45:58 +02:00
// Legacy - id ending in [] means multiple
if ( this . options . id . substr ( - 2 ) == "[]" )
{
this . options . multiple = true ;
}
2011-09-02 00:07:30 +02:00
// Set up the URL to have the request ID & the widget ID
var instance = this . getInstanceManager ( ) ;
2011-09-08 22:33:46 +02:00
2011-09-02 00:07:30 +02:00
var self = this ;
2012-03-30 00:45:58 +02:00
2012-03-29 01:27:18 +02:00
this . asyncOptions = jQuery . extend ( {
2011-09-02 00:07:30 +02:00
// Callbacks
2012-03-30 00:45:58 +02:00
onStart : function ( event , file _count ) {
// Hide any previous errors
self . hideMessage ( ) ;
return self . onStart ( event , file _count ) ;
} ,
onFinish : function ( event , file _count ) {
2013-08-10 01:34:42 +02:00
self . onFinish . apply ( self , [ event , file _count ] ) ;
2012-03-30 00:45:58 +02:00
// Fire legacy change action when done
self . change ( self . input ) ;
} ,
onStartOne : function ( event , file _name , index , file _count ) {
// Here 'this' is the input
if ( self . checkMime ( this . files [ index ] ) )
{
return self . createStatus ( event , file _name , index , file _count ) ;
}
else
{
// Wrong mime type - show in the list of files
return self . createStatus (
self . egw ( ) . lang ( "File is of wrong type (%1 != %2)!" , this . files [ index ] . type , self . options . mime ) ,
file _name
) ;
}
} ,
2011-09-02 00:07:30 +02:00
onFinishOne : function ( event , response , name , number , total ) { return self . finishUpload ( event , response , name , number , total ) ; } ,
2011-09-06 21:55:52 +02:00
onProgress : function ( event , progress , name , number , total ) { return self . onProgress ( event , progress , name , number , total ) ; } ,
onError : function ( event , name , error ) { return self . onError ( event , name , error ) ; } ,
2011-09-02 00:07:30 +02:00
sendBoundary : window . FormData || jQuery . browser . mozilla ,
2011-09-08 22:33:46 +02:00
beforeSend : function ( form ) { return self . beforeSend ( form ) ; } ,
2013-09-10 19:55:05 +02:00
url : egw . ajaxUrl ( "etemplate_widget_file::ajax_upload::etemplate" ) ,
2012-03-30 00:45:58 +02:00
autoclear : ! ( this . options . onchange )
2012-03-29 01:27:18 +02:00
} , this . asyncOptions ) ;
2011-09-02 00:07:30 +02:00
this . asyncOptions . fieldName = this . options . id ;
2011-09-01 01:37:30 +02:00
this . createInputWidget ( ) ;
} ,
2011-09-08 22:33:46 +02:00
destroy : function ( ) {
2013-07-20 19:20:55 +02:00
this . _super . apply ( this , arguments ) ;
2013-08-03 21:12:38 +02:00
this . set _drop _target ( null ) ;
2011-09-08 22:33:46 +02:00
this . node = null ;
this . input = null ;
this . progress = null ;
} ,
2011-09-01 01:37:30 +02:00
createInputWidget : function ( ) {
this . node = $j ( document . createElement ( "div" ) ) . addClass ( "et2_file" ) ;
this . input = $j ( document . createElement ( "input" ) )
. attr ( "type" , "file" ) . attr ( "placeholder" , this . options . blur )
2011-09-02 00:07:30 +02:00
. appendTo ( this . node ) ;
// Check for File interface, should fall back to normal form submit if missing
if ( typeof File != "undefined" && typeof ( new XMLHttpRequest ( ) ) . upload != "undefined" )
{
this . input . html5 _upload ( this . asyncOptions ) ;
}
2011-09-06 21:55:52 +02:00
else
{
// This may be a problem submitting via ajax
}
2013-09-10 00:04:48 +02:00
if ( this . options . progress )
{
2013-09-10 10:08:47 +02:00
var widget = this . getRoot ( ) . getWidgetById ( this . options . progress ) ;
if ( widget )
2013-09-10 00:04:48 +02:00
{
2013-09-10 14:57:40 +02:00
//may be not available at createInputWidget time
2013-09-10 10:08:47 +02:00
this . progress = $j ( widget . getDOMNode ( ) ) ;
2013-09-10 00:04:48 +02:00
}
}
if ( ! this . progress )
{
this . progress = $j ( document . createElement ( "div" ) ) . appendTo ( this . node ) ;
}
2011-09-01 01:37:30 +02:00
this . progress . addClass ( "progress" ) ;
2012-03-30 00:45:58 +02:00
if ( this . options . multiple )
{
2011-09-01 01:37:30 +02:00
this . input . attr ( "multiple" , "multiple" ) ;
}
2012-03-30 00:45:58 +02:00
2011-09-01 01:37:30 +02:00
this . setDOMNode ( this . node [ 0 ] ) ;
2013-08-03 21:12:38 +02:00
} ,
/ * *
* Set a widget or DOM node as a HTML5 file drop target
*
* @ param String new _target widget ID or DOM node ID to be used as a new target
* /
set _drop _target : function ( new _target )
{
// Cancel old drop target
if ( this . options . drop _target )
{
var widget = this . getRoot ( ) . getWidgetById ( this . options . drop _target ) ;
var drop _target = widget && widget . getDOMNode ( ) || document . getElementById ( this . options . drop _target ) ;
2013-08-27 18:29:13 +02:00
$j ( drop _target ) . off ( "." + this . id ) ;
2013-08-03 21:12:38 +02:00
}
this . options . drop _target = new _target ;
2013-08-27 18:29:13 +02:00
if ( ! this . options . drop _target ) return ;
2013-08-03 21:12:38 +02:00
// Set up new drop target
var widget = this . getRoot ( ) . getWidgetById ( this . options . drop _target ) ;
var drop _target = widget && widget . getDOMNode ( ) || document . getElementById ( this . options . drop _target ) ;
if ( drop _target )
{
2013-08-13 22:46:23 +02:00
// Tell jQuery to include this property
jQuery . event . props . push ( 'dataTransfer' ) ;
2013-08-03 21:12:38 +02:00
var self = this ;
drop _target . ondrop = function ( event ) {
return false ;
} ;
$j ( drop _target )
2013-08-27 18:29:13 +02:00
. on ( "drop." + this . id , jQuery . proxy ( function ( event ) {
2013-08-10 01:34:42 +02:00
event . stopPropagation ( ) ;
event . preventDefault ( ) ;
this . input . removeClass ( "ui-state-active" ) ;
2013-08-03 21:12:38 +02:00
if ( event . dataTransfer && event . dataTransfer . files . length > 0 )
{
this . input [ 0 ] . files = event . dataTransfer . files ;
}
2013-08-10 01:34:42 +02:00
return false ;
2013-08-03 21:12:38 +02:00
} , this ) )
2013-08-27 18:29:13 +02:00
. on ( "dragenter." + this . id , function ( e ) {
2013-08-10 01:34:42 +02:00
// Accept the drop if at least one mimetype matches
// Individual files will be rejected later
var mime _ok = false ;
for ( var file in e . dataTransfer . files )
{
mime _ok = mime _ok || self . checkMime ( file ) ;
}
if ( ! self . disabled && mime _ok )
{
self . input . addClass ( "ui-state-active" ) ;
}
// Returning true cancels, return false to allow
return self . disabled || ! mime _ok ;
} )
2013-08-27 18:29:13 +02:00
. on ( "dragleave." + this . id , function ( e ) {
2013-08-10 01:34:42 +02:00
self . input . removeClass ( "ui-state-active" ) ;
// Returning true cancels, return false to allow
return self . disabled
2013-08-03 21:12:38 +02:00
} )
2013-08-27 18:29:13 +02:00
. on ( "dragover." + this . id , function ( e ) {
2013-08-10 01:34:42 +02:00
// Returning true cancels, return false to allow
return self . disabled ;
2013-08-03 21:12:38 +02:00
} )
2013-08-27 18:29:13 +02:00
. on ( "dragend." + this . id , false ) ;
2013-08-03 21:12:38 +02:00
}
else
{
this . egw ( ) . debug ( "warn" , "Did not find file drop target %s" , this . options . drop _target ) ;
}
2012-03-30 00:45:58 +02:00
} ,
attachToDOM : function ( ) {
this . _super . apply ( this , arguments ) ;
// Override parent's behaviour to fire legacy change when finished
if ( this . onchange )
{
this . input . unbind ( "change.et2_inputWidget" ) ;
}
2011-09-01 01:37:30 +02:00
} ,
2011-09-06 21:55:52 +02:00
getValue : function ( ) {
var value = this . options . value ? this . options . value : this . input . val ( ) ;
return value ;
} ,
2013-04-22 18:52:03 +02:00
/ * *
2013-08-27 19:20:08 +02:00
* Set the value of the file widget .
*
* If you pass a FileList or list of files , it will trigger the async upload
*
* @ param { FileList | File [ ] | false } value List of files to be uploaded , or false to reset .
2013-04-22 18:52:03 +02:00
* /
set _value : function ( value ) {
if ( ! value || typeof value == "undefined" )
{
value = { } ;
}
if ( jQuery . isEmptyObject ( value ) )
{
this . options . value = { } ;
this . progress . empty ( ) ;
// Reset the HTML element
this . input . wrap ( '<form>' ) . closest ( 'form' ) . get ( 0 ) . reset ( ) ;
this . input . unwrap ( ) ;
return ;
}
2013-08-27 19:20:08 +02:00
if ( typeof value == 'object' && value . length && typeof value [ 0 ] == 'object' && value [ 0 ] . name )
2013-04-22 18:52:03 +02:00
{
2013-08-27 19:20:08 +02:00
this . input [ 0 ] . files = value ;
2013-04-22 18:52:03 +02:00
}
} ,
2011-09-01 01:37:30 +02:00
getInputNode : function ( ) {
return this . input [ 0 ] ;
} ,
2012-03-30 00:45:58 +02:00
set _mime : function ( mime ) {
if ( ! mime )
{
this . options . mime = null ;
}
if ( mime . indexOf ( "/" ) != 0 )
{
// Lower case it now, if it's not a regex
this . options . mime = mime . toLowerCase ( ) ;
}
else
{
// Convert into a js regex
2013-04-13 21:00:13 +02:00
var parts = mime . substr ( 1 ) . match ( /(.*)\/([igm]?)$/ ) ;
2012-03-30 00:45:58 +02:00
this . options . mime = new RegExp ( parts [ 1 ] , parts . length > 2 ? parts [ 2 ] : "" ) ;
}
} ,
set _multiple : function ( _multiple ) {
this . options . multiple = _multiple ;
if ( _multiple )
{
return this . input . attr ( "multiple" , "multiple" ) ;
}
return this . input . removeAttr ( "multiple" ) ;
} ,
/ * *
* Check to see if the provided file ' s mimetype matches
*
* @ param f File object
* @ return boolean
* /
checkMime : function ( f ) {
// If missing, let the server handle it
if ( ! this . options . mime || ! f . type ) return true ;
var is _preg = ( typeof this . options . mime == "object" ) ;
if ( ! is _preg && f . type . toLowerCase ( ) == this . options . mime || is _preg && this . options . mime . test ( f . type ) )
{
return true ;
}
// Not right mime
return false ;
} ,
2011-09-08 22:33:46 +02:00
/ * *
* Add in the request id
* /
beforeSend : function ( form ) {
var instance = this . getInstanceManager ( ) ;
// Form object available, mess with it directly
if ( form ) {
return form . append ( "request_id" , instance . etemplate _exec _id ) ;
}
// No Form object, add in as string
return 'Content-Disposition: form-data; name="request_id"\r\n\r\n' + instance . etemplate _exec _id + '\r\n' ;
} ,
2011-09-06 21:55:52 +02:00
/ * *
* Disables submit buttons while uploading
* /
onStart : function ( event , file _count ) {
this . disabled _buttons = $j ( "input[type='submit'], button" ) . not ( "[disabled]" ) . attr ( "disabled" , true ) ;
2011-09-09 02:05:18 +02:00
event . data = this ;
2013-04-20 21:21:42 +02:00
if ( this . options . onStart ) return et2 _call ( this . options . onStart , event , file _count ) ;
2011-09-06 21:55:52 +02:00
return true ;
} ,
/ * *
* Re - enables submit buttons when done
* /
onFinish : function ( event , file _count ) {
this . disabled _buttons . attr ( "disabled" , false ) ;
2011-09-09 02:05:18 +02:00
event . data = this ;
2013-04-20 21:21:42 +02:00
if ( this . options . onFinish ) return et2 _call ( this . options . onFinish , event , file _count ) ;
2011-09-06 21:55:52 +02:00
} ,
2011-09-01 01:37:30 +02:00
/ * *
2011-09-02 00:07:30 +02:00
* Creates the elements used for displaying the file , and it ' s upload status , and
* attaches them to the DOM
2012-03-30 00:45:58 +02:00
*
* @ param _event Either the event , or an error message
2011-09-01 01:37:30 +02:00
* /
2012-03-30 00:45:58 +02:00
createStatus : function ( _event , file _name , index , file _count ) {
var error = ( typeof _event == "object" ? "" : _event ) ;
2011-09-06 21:55:52 +02:00
if ( this . input [ 0 ] . files [ index ] ) {
var file = this . input [ 0 ] . files [ index ] ;
if ( file . size > this . options . max _file _size ) {
2012-05-22 22:44:06 +02:00
error = this . egw ( ) . lang ( "File too large. Maximum %1" , et2 _vfsSize . prototype . human _size ( this . options . max _file _size ) ) ;
2011-09-06 21:55:52 +02:00
}
}
2013-09-10 14:57:40 +02:00
if ( this . options . progress )
{
var widget = this . getRoot ( ) . getWidgetById ( this . options . progress ) ;
if ( widget )
{
this . progress = $j ( widget . getDOMNode ( ) ) ;
this . progress . addClass ( "progress" ) ;
}
}
2011-09-02 00:07:30 +02:00
if ( this . progress )
{
2011-09-06 21:55:52 +02:00
var status = $j ( "<li file='" + file _name + "'>" + file _name
+ "<div class='remove'/><span class='progressBar'><p/></span></li>" )
2011-09-09 02:05:18 +02:00
. appendTo ( this . progress ) ;
$j ( "div.remove" , status ) . click ( this , this . cancel ) ;
2011-09-06 21:55:52 +02:00
if ( error != "" )
{
2013-08-10 01:34:42 +02:00
status . addClass ( "message ui-state-error" ) ;
2011-09-06 21:55:52 +02:00
status . append ( "<div>" + error + "</diff>" ) ;
$j ( ".progressBar" , status ) . css ( "display" , "none" ) ;
}
}
return error == "" ;
} ,
onProgress : function ( event , percent , name , number , total ) {
if ( this . progress )
{
$j ( "li[file='" + name + "'] > span.progressBar > p" ) . css ( "width" , Math . ceil ( percent * 100 ) + "%" ) ;
2011-09-01 01:37:30 +02:00
}
2011-09-02 00:07:30 +02:00
return true ;
2011-09-01 01:37:30 +02:00
} ,
2011-09-06 21:55:52 +02:00
onError : function ( event , name , error ) {
2013-09-10 14:57:40 +02:00
console . warn ( event , name , error ) ;
2011-09-06 21:55:52 +02:00
} ,
2011-09-01 01:37:30 +02:00
/ * *
2011-09-02 00:07:30 +02:00
* A file upload is finished , update the UI
2011-09-01 01:37:30 +02:00
* /
2011-09-02 00:07:30 +02:00
finishUpload : function ( event , response , name , number , total ) {
2011-09-06 21:55:52 +02:00
if ( typeof response == 'string' ) response = jQuery . parseJSON ( response ) ;
2012-03-30 00:45:58 +02:00
if ( response . response [ 0 ] && typeof response . response [ 0 ] . data . length == 'undefined' ) {
2011-09-06 21:55:52 +02:00
if ( typeof this . options . value != 'object' ) this . options . value = { } ;
for ( var key in response . response [ 0 ] . data ) {
2012-03-30 00:45:58 +02:00
if ( typeof response . response [ 0 ] . data [ key ] == "string" )
{
// Message from server - probably error
$j ( "[file='" + name + "']" , this . progress )
. addClass ( "error" )
. css ( "display" , "block" )
. text ( response . response [ 0 ] . data [ key ] ) ;
}
else
{
this . options . value [ key ] = response . response [ 0 ] . data [ key ] ;
if ( this . progress )
{
$j ( "[file='" + name + "']" , this . progress ) . addClass ( "message success" ) ;
}
}
2011-09-06 21:55:52 +02:00
}
}
else if ( this . progress )
2011-09-02 00:07:30 +02:00
{
2012-03-30 00:45:58 +02:00
$j ( "[file='" + name + "']" , this . progress )
2013-08-10 01:34:42 +02:00
. addClass ( "ui-state-error" )
2012-03-30 00:45:58 +02:00
. css ( "display" , "block" )
. text ( this . egw ( ) . lang ( "Server error" ) ) ;
2011-09-06 21:55:52 +02:00
}
2013-04-20 21:21:42 +02:00
if ( this . options . onFinishOne )
2012-04-24 18:33:56 +02:00
{
2013-04-20 21:21:42 +02:00
return et2 _call ( this . options . onFinishOne , event , response , name , number , total ) ;
2012-04-24 18:33:56 +02:00
}
2011-09-06 21:55:52 +02:00
return true ;
} ,
/ * *
2013-04-22 18:52:03 +02:00
* Remove a file from the list of values
2011-09-06 21:55:52 +02:00
* /
2013-04-22 18:52:03 +02:00
remove _file : function ( filename )
{
2013-09-10 14:57:40 +02:00
//console.info(filename);
2013-04-22 18:52:03 +02:00
for ( var key in this . options . value )
{
if ( this . options . value [ key ] . name == filename )
2011-09-06 21:55:52 +02:00
{
2013-04-22 18:52:03 +02:00
delete this . options . value [ key ] ;
$j ( '[file="' + filename + '"]' , this . node ) . remove ( ) ;
2011-09-06 21:55:52 +02:00
return ;
}
2011-09-02 00:07:30 +02:00
}
2013-04-22 18:52:03 +02:00
} ,
/ * *
* Cancel a file - event callback
* /
cancel : function ( e )
{
e . preventDefault ( ) ;
// Look for file name in list
var target = $j ( e . target ) . parents ( "li.message" ) ;
2013-08-10 01:34:42 +02:00
e . data . remove _file . apply ( e . data , [ target . attr ( "file" ) ] ) ;
2011-09-06 21:55:52 +02:00
// In case it didn't make it to the list (error)
2011-09-09 02:05:18 +02:00
target . remove ( ) ;
2011-09-06 21:55:52 +02:00
$j ( e . target ) . remove ( ) ;
2011-09-01 01:37:30 +02:00
}
} ) ;
et2 _register _widget ( et2 _file , [ "file" ] ) ;