2011-09-06 13:05:25 +00:00
openerp . web . data _import = function ( openerp ) {
2011-10-19 10:38:22 +00:00
var QWeb = openerp . web . qweb ,
_t = openerp . web . _t ;
2011-09-09 10:53:07 +00:00
/ * *
* Safari does not deal well at all with raw JSON data being returned . As a
* result , we ' re going to cheat by using a pseudo - jsonp : instead of getting
* JSON data in the iframe , we ' re getting a ` ` script ` ` tag which consists of a
* function call and the returned data ( the json dump ) .
*
* The function is an auto - generated name bound to ` ` window ` ` , which calls
* back into the callback provided here .
*
* @ param { Object } form the form element ( DOM or jQuery ) to use in the call
* @ param { Object } attributes jquery . form attributes object
* @ param { Function } callback function to call with the returned data
* /
function jsonp ( form , attributes , callback ) {
2011-09-23 12:22:02 +00:00
attributes = attributes || { } ;
2011-09-09 10:53:07 +00:00
var options = { jsonp : _ . uniqueId ( 'import_callback_' ) } ;
window [ options . jsonp ] = function ( ) {
delete window [ options . jsonp ] ;
callback . apply ( null , arguments ) ;
} ;
2011-09-23 12:22:02 +00:00
if ( 'data' in attributes ) {
_ . extend ( attributes . data , options ) ;
} else {
_ . extend ( attributes , { data : options } ) ;
}
$ ( form ) . ajaxSubmit ( attributes ) ;
2011-09-09 10:53:07 +00:00
}
2011-09-23 08:33:41 +00:00
2011-09-06 13:05:25 +00:00
openerp . web . DataImport = openerp . web . Dialog . extend ( {
2011-09-09 11:49:33 +00:00
template : 'ImportDataView' ,
2011-12-15 10:40:31 +00:00
dialog _title : { toString : function ( ) { return _t ( "Import Data" ) ; } } ,
2011-08-03 13:01:46 +00:00
init : function ( parent , dataset ) {
2011-09-23 08:33:41 +00:00
var self = this ;
2011-09-21 10:09:00 +00:00
this . _super ( parent , { } ) ;
2011-09-23 08:33:41 +00:00
this . model = parent . model ;
this . fields = [ ] ;
this . all _fields = [ ] ;
2011-10-18 10:21:08 +00:00
this . fields _with _defaults = [ ] ;
2011-09-23 09:45:00 +00:00
this . required _fields = null ;
2011-09-23 08:33:41 +00:00
var convert _fields = function ( root , prefix ) {
prefix = prefix || '' ;
_ ( root . fields ) . each ( function ( f ) {
2011-09-23 12:46:34 +00:00
self . all _fields . push ( prefix + f . name ) ;
2011-09-23 08:33:41 +00:00
if ( f . fields ) {
2011-09-23 12:46:34 +00:00
convert _fields ( f , prefix + f . id + '/' ) ;
2011-09-23 08:33:41 +00:00
}
} ) ;
} ;
this . ready = $ . Deferred . queue ( ) . then ( function ( ) {
self . required _fields = _ ( self . fields ) . chain ( )
2011-10-18 10:21:08 +00:00
. filter ( function ( field ) {
return field . required &&
! _ . include ( self . fields _with _defaults , field . id ) ; } )
2011-09-23 08:33:41 +00:00
. pluck ( 'name' )
. value ( ) ;
convert _fields ( self ) ;
self . all _fields . sort ( ) ;
} ) ;
2011-07-27 06:43:25 +00:00
} ,
start : function ( ) {
2011-09-06 12:11:46 +00:00
var self = this ;
2011-09-21 10:09:00 +00:00
this . _super ( ) ;
this . open ( {
2011-09-21 10:13:20 +00:00
modal : true ,
width : '70%' ,
height : 'auto' ,
position : 'top' ,
2011-09-21 11:25:56 +00:00
buttons : [
2011-11-14 10:12:21 +00:00
{ text : _t ( "Close" ) , click : function ( ) { self . stop ( ) ; } } ,
{ text : _t ( "Import File" ) , click : function ( ) { self . do _import ( ) ; } , 'class' : 'oe-dialog-import-button' }
2011-09-21 11:25:56 +00:00
] ,
2011-09-21 10:13:20 +00:00
close : function ( event , ui ) {
2011-09-21 10:09:00 +00:00
self . stop ( ) ;
}
} ) ;
2011-09-21 11:25:56 +00:00
this . toggle _import _button ( false ) ;
2011-08-03 12:32:42 +00:00
this . $element . find ( '#csvfile' ) . change ( this . on _autodetect _data ) ;
2011-08-16 06:08:35 +00:00
this . $element . find ( 'fieldset' ) . change ( this . on _autodetect _data ) ;
2011-10-24 14:06:58 +00:00
this . $element . delegate ( 'fieldset legend' , 'click' , function ( ) {
2011-10-24 14:17:37 +00:00
$ ( this ) . parent ( ) . toggleClass ( 'oe-closed' ) ;
2011-08-03 12:32:42 +00:00
} ) ;
2011-09-23 08:33:41 +00:00
this . ready . push ( new openerp . web . DataSet ( this , this . model ) . call (
'fields_get' , [ ] , function ( fields ) {
self . graft _fields ( fields ) ;
2011-10-18 10:21:08 +00:00
self . ready . push ( new openerp . web . DataSet ( self , self . model )
. default _get ( _ . pluck ( self . fields , 'id' ) , function ( fields ) {
_ . each ( fields , function ( val , key ) {
if ( val ) {
self . fields _with _defaults . push ( key ) ;
}
} ) ;
2011-10-17 06:16:02 +00:00
} )
)
} ) ) ;
2011-09-23 08:33:41 +00:00
} ,
graft _fields : function ( fields , parent , level ) {
parent = parent || this ;
level = level || 0 ;
var self = this ;
2011-10-19 10:38:22 +00:00
if ( level === 0 ) {
parent . fields . push ( {
id : 'id' ,
name : 'id' ,
string : _t ( 'External ID' ) ,
required : false
} ) ;
}
2011-09-23 08:33:41 +00:00
_ ( fields ) . each ( function ( field , field _name ) {
2011-12-16 11:49:41 +00:00
// Ignore spec for id field
// Don't import function fields (function and related)
if ( field _name === 'id' || 'function' in field ) {
return ;
}
2011-09-23 08:33:41 +00:00
var f = {
2011-09-23 12:46:34 +00:00
id : field _name ,
2011-09-23 08:33:41 +00:00
name : field _name ,
string : field . string ,
required : field . required
} ;
2011-09-23 12:46:34 +00:00
switch ( field . type ) {
case 'many2many' :
case 'many2one' :
f . name += '/id' ;
break ;
case 'one2many' :
f . name += '/id' ;
2011-09-23 08:33:41 +00:00
f . fields = [ ] ;
// only fetch sub-fields to a depth of 2 levels
if ( level < 2 ) {
self . ready . push ( new openerp . web . DataSet ( self , field . relation ) . call (
'fields_get' , [ ] , function ( fields ) {
self . graft _fields ( fields , f , level + 1 ) ;
} ) ) ;
}
2011-09-23 12:46:34 +00:00
break ;
2011-09-23 08:33:41 +00:00
}
parent . fields . push ( f ) ;
} ) ;
2011-08-03 12:32:42 +00:00
} ,
2011-09-21 11:25:56 +00:00
toggle _import _button : function ( newstate ) {
this . $dialog . dialog ( 'widget' )
. find ( '.oe-dialog-import-button' )
. button ( 'option' , 'disabled' , ! newstate ) ;
} ,
2011-08-03 12:32:42 +00:00
do _import : function ( ) {
2011-09-21 10:13:20 +00:00
if ( ! this . $element . find ( '#csvfile' ) . val ( ) ) { return ; }
2011-09-23 12:22:02 +00:00
var lines _to _skip = parseInt ( this . $element . find ( '#csv_skip' ) . val ( ) , 10 ) ;
var with _headers = this . $element . find ( '#file_has_headers' ) . prop ( 'checked' ) ;
if ( ! lines _to _skip && with _headers ) {
lines _to _skip = 1 ;
}
var indices = [ ] , fields = [ ] ;
this . $element . find ( ".sel_fields" ) . each ( function ( index , element ) {
var val = element . value ;
if ( ! val ) {
return ;
}
indices . push ( index ) ;
fields . push ( val ) ;
} ) ;
2011-09-21 10:13:20 +00:00
jsonp ( this . $element . find ( '#import_data' ) , {
2011-09-23 12:22:02 +00:00
url : '/web/import/import_data' ,
data : {
model : this . model ,
meta : JSON . stringify ( {
skip : lines _to _skip ,
indices : indices ,
fields : fields
} )
}
2011-09-21 10:13:20 +00:00
} , this . on _import _results ) ;
2011-08-03 12:32:42 +00:00
} ,
on _autodetect _data : function ( ) {
2011-09-21 10:13:20 +00:00
if ( ! this . $element . find ( '#csvfile' ) . val ( ) ) { return ; }
jsonp ( this . $element . find ( '#import_data' ) , {
url : '/web/import/detect_data'
} , this . on _import _results ) ;
2011-08-03 12:32:42 +00:00
} ,
2011-09-21 10:13:20 +00:00
on _import _results : function ( results ) {
2011-09-23 11:06:05 +00:00
this . $element . find ( '#result' ) . empty ( ) ;
2011-09-26 10:07:44 +00:00
var headers , result _node = this . $element . find ( "#result" ) ;
2011-09-06 12:11:46 +00:00
2011-12-01 11:01:40 +00:00
if ( results [ 'error' ] ) {
result _node . append ( QWeb . render ( 'ImportView.error' , {
'error' : results [ 'error' ] } ) ) ;
this . $element . find ( 'fieldset' ) . removeClass ( 'oe-closed' ) ;
return ;
}
if ( results [ 'success' ] ) {
if ( this . widget _parent . widget _parent . active _view == "list" ) {
this . widget _parent . reload _content ( ) ;
}
this . stop ( ) ;
return ;
}
2011-09-21 10:13:20 +00:00
if ( results [ 'records' ] ) {
2011-09-26 10:07:44 +00:00
var lines _to _skip = parseInt ( this . $element . find ( '#csv_skip' ) . val ( ) , 10 ) ,
with _headers = this . $element . find ( '#file_has_headers' ) . prop ( 'checked' ) ;
headers = with _headers ? results . records [ 0 ] : null ;
2011-09-23 09:23:46 +00:00
result _node . append ( QWeb . render ( 'ImportView.result' , {
2011-09-26 10:07:44 +00:00
'headers' : headers ,
2011-09-23 09:56:09 +00:00
'records' : lines _to _skip ? results . records . slice ( lines _to _skip )
: with _headers ? results . records . slice ( 1 )
: results . records
2011-09-23 09:23:46 +00:00
} ) ) ;
2011-10-24 14:27:44 +00:00
this . $element . find ( 'fieldset' ) . addClass ( 'oe-closed' ) ;
2011-08-03 12:32:42 +00:00
}
2011-10-24 14:27:44 +00:00
this . $element . find ( 'form' ) . removeClass ( 'oe-import-no-result' ) ;
[FIX] mitigate horrendous performance issues inserting options in Webkit
In some cases, at least with complex-enough views, inserting many
options in a document in a row will get progressively slower.
In import, this issue is hit on trying to import partners: partners
have a humongous number of fields (direct and on their o2m), ~940,
which yields a correspondingly huge number of options in the
selection.
A basic partner export also has quite high a number of columns (~50
without exporting o2m fields), so this list of 940 options is inserted
50 times in a row (literally too, they're all in the same table row)..
While not all that fast, Firefox 5/6 has no significant issue with
this (~18ms/insertion, where an insertion is a full select with all
its options). Webkit browsers (Chrome and Safari) on the other hand
start out fair (~10ms/insertion), but get slower and slower until they
end up at 3~5 *seconds* for each insertion (3s if inserting a
DocumentFragment, 5s if inserting text via innerHTML). This means the
preview table takes up to *two minutes* to display, even the best
cases (pre-generating everything that can be and optimizing everything
I could think of) take 75 *seconds* for the insertions (the
pregeneration of a given select and its options is ~100ms, the base
template rendering is ~20ms).
rendering divs or inputs does not have this issue, I did not manage to
reduce or fix the issue directly so I replaced the options by
jQuery-ui's autocomplete widget. This is not issues-free: when tabbing
through the fields lists, when reaching the edge of the popup the
browser will automatically scroll the field back into view. However,
this is done *after* autocomplete's popup has opened, and as a result
the popup opens in the wrong place (at the popup's edge, instead of
under the now-moved field).
bzr revid: xmo@openerp.com-20110922085812-3u1esk6czraskm01
2011-09-22 08:58:12 +00:00
2011-10-24 13:55:21 +00:00
this . $element . delegate ( '.oe-m2o-drop-down-button' , 'click' , function ( ) {
$ ( this ) . prev ( 'input' ) . focus ( ) ;
} ) ;
2011-09-22 09:54:09 +00:00
var self = this ;
2011-09-23 09:23:46 +00:00
this . ready . then ( function ( ) {
2011-09-26 10:07:44 +00:00
var $fields = self . $element . find ( '.sel_fields' ) . bind ( 'blur' , function ( ) {
2011-09-26 09:30:24 +00:00
if ( this . value && ! _ ( self . all _fields ) . contains ( this . value ) ) {
this . value = '' ;
}
} ) . autocomplete ( {
2011-09-23 09:23:46 +00:00
minLength : 0 ,
source : self . all _fields ,
change : self . on _check _field _values
} ) . focus ( function ( ) {
$ ( this ) . autocomplete ( 'search' ) ;
} ) ;
2011-09-26 10:07:44 +00:00
// Column auto-detection
_ ( headers ) . each ( function ( header , index ) {
2011-10-19 13:49:36 +00:00
var field _name = self . match _column _to _field ( header ) ;
if ( field _name ) {
$fields . eq ( index ) . val ( field _name ) ;
2011-09-26 10:07:44 +00:00
}
} ) ;
2011-09-23 09:23:46 +00:00
self . on _check _field _values ( ) ;
[FIX] mitigate horrendous performance issues inserting options in Webkit
In some cases, at least with complex-enough views, inserting many
options in a document in a row will get progressively slower.
In import, this issue is hit on trying to import partners: partners
have a humongous number of fields (direct and on their o2m), ~940,
which yields a correspondingly huge number of options in the
selection.
A basic partner export also has quite high a number of columns (~50
without exporting o2m fields), so this list of 940 options is inserted
50 times in a row (literally too, they're all in the same table row)..
While not all that fast, Firefox 5/6 has no significant issue with
this (~18ms/insertion, where an insertion is a full select with all
its options). Webkit browsers (Chrome and Safari) on the other hand
start out fair (~10ms/insertion), but get slower and slower until they
end up at 3~5 *seconds* for each insertion (3s if inserting a
DocumentFragment, 5s if inserting text via innerHTML). This means the
preview table takes up to *two minutes* to display, even the best
cases (pre-generating everything that can be and optimizing everything
I could think of) take 75 *seconds* for the insertions (the
pregeneration of a given select and its options is ~100ms, the base
template rendering is ~20ms).
rendering divs or inputs does not have this issue, I did not manage to
reduce or fix the issue directly so I replaced the options by
jQuery-ui's autocomplete widget. This is not issues-free: when tabbing
through the fields lists, when reaching the edge of the popup the
browser will automatically scroll the field back into view. However,
this is done *after* autocomplete's popup has opened, and as a result
the popup opens in the wrong place (at the popup's edge, instead of
under the now-moved field).
bzr revid: xmo@openerp.com-20110922085812-3u1esk6czraskm01
2011-09-22 08:58:12 +00:00
} ) ;
2011-09-15 06:47:53 +00:00
} ,
2011-10-19 13:49:36 +00:00
/ * *
* Returns the name of the field ( nested ) matching the provided column name
*
* @ param { String } name column name to look for
* @ param { Array } [ fields ] fields to look into for the provided name
* @ returns { String | undefined }
* /
match _column _to _field : function ( name , fields ) {
fields = fields || this . fields ;
var f ;
f = _ ( fields ) . detect ( function ( field ) {
// TODO: levenshtein between header and field.string
return field . name === name
|| field . string . toLowerCase ( ) === name . toLowerCase ( ) ;
} ) ;
if ( f ) { return f . name ; }
// if ``name`` is a path (o2m), we need to recurse through its .fields
var index = name . indexOf ( '/' ) ;
if ( index === - 1 ) { return undefined ; }
// Get the first path section, try to find the matching field
var column _name = name . substring ( 0 , index ) ;
f = _ ( fields ) . detect ( function ( field ) {
// field.name for o2m is $foo/id, so we want to match on id
return field . id === column _name
|| field . string . toLowerCase ( ) === column _name . toLowerCase ( )
} ) ;
if ( ! f ) { return undefined ; }
// if we found a matching field for the first path section, recurse in
// its own .fields to try and get the rest of the path matched
var rest = this . match _column _to _field (
name . substring ( index + 1 ) , f . fields ) ;
if ( ! rest ) { return undefined ; }
return f . id + '/' + rest ;
} ,
2011-09-22 12:13:27 +00:00
/ * *
* Looks through all the field selections , and tries to find if two
* ( or more ) columns were matched to the same model field .
*
* Returns a map of the multiply - mapped fields to an array of offending
* columns ( not actually columns , but the inputs containing the same field
* names ) .
*
* Also has the side - effect of marking the discovered inputs with the class
* ` ` duplicate _fld ` ` .
*
* @ returns { Object < String , Array < String >> } map of duplicate field matches to same - valued inputs
* /
find _duplicate _fields : function ( ) {
2011-09-22 10:59:48 +00:00
// Maps values to DOM nodes, in order to discover duplicates
var values = { } , duplicates = { } ;
2011-09-22 12:08:10 +00:00
this . $element . find ( ".sel_fields" ) . each ( function ( index , element ) {
2011-09-22 10:59:48 +00:00
var value = element . value ;
var $element = $ ( element ) . removeClass ( 'duplicate_fld' ) ;
if ( ! value ) { return ; }
if ( ! ( value in values ) ) {
values [ value ] = element ;
2011-09-21 10:13:20 +00:00
} else {
2011-09-22 10:59:48 +00:00
var same _valued _field = values [ value ] ;
if ( value in duplicates ) {
duplicates [ value ] . push ( element ) ;
} else {
duplicates [ value ] = [ same _valued _field , element ] ;
}
$element . add ( same _valued _field ) . addClass ( 'duplicate_fld' ) ;
2011-08-29 12:47:43 +00:00
}
2011-09-21 10:13:20 +00:00
} ) ;
2011-09-22 12:13:27 +00:00
return duplicates ;
} ,
2011-09-23 08:33:41 +00:00
on _check _field _values : function ( ) {
2011-09-22 12:13:27 +00:00
this . $element . find ( "#message, #msg" ) . remove ( ) ;
2011-09-23 08:33:41 +00:00
var required _valid = this . check _required ( ) ;
2011-09-22 10:59:48 +00:00
2011-09-22 12:13:27 +00:00
var duplicates = this . find _duplicate _fields ( ) ;
2011-09-22 12:08:10 +00:00
if ( _ . isEmpty ( duplicates ) ) {
this . toggle _import _button ( required _valid ) ;
} else {
var $err = $ ( '<div id="msg" style="color: red;">Destination fields should only be selected once, some fields are selected more than once:</div>' ) . insertBefore ( this . $element . find ( '#result' ) ) ;
2011-09-22 10:59:48 +00:00
var $dupes = $ ( '<dl>' ) . appendTo ( $err ) ;
2011-09-22 12:08:10 +00:00
_ ( duplicates ) . each ( function ( elements , value ) {
2011-09-22 10:59:48 +00:00
$ ( '<dt>' ) . text ( value ) . appendTo ( $dupes ) ;
2011-09-22 12:08:10 +00:00
_ ( elements ) . each ( function ( element ) {
2011-09-22 10:59:48 +00:00
var cell = $ ( element ) . closest ( 'td' ) ;
2011-09-22 12:08:10 +00:00
$ ( '<dd>' ) . text ( cell . parent ( ) . children ( ) . index ( cell ) ) . appendTo ( $dupes ) ;
2011-09-22 10:59:48 +00:00
} ) ;
} ) ;
2011-09-21 11:25:56 +00:00
this . toggle _import _button ( false ) ;
2011-09-21 10:13:20 +00:00
}
2011-09-22 10:59:48 +00:00
2011-08-03 12:32:42 +00:00
} ,
2011-09-23 08:33:41 +00:00
check _required : function ( ) {
if ( ! this . required _fields . length ) { return true ; }
2011-09-21 10:14:36 +00:00
2011-09-22 12:03:28 +00:00
var selected _fields = _ ( this . $element . find ( '.sel_fields' ) . get ( ) ) . chain ( )
. pluck ( 'value' )
. compact ( )
. value ( ) ;
2011-09-23 08:33:41 +00:00
var missing _fields = _ . difference ( this . required _fields , selected _fields ) ;
2011-09-22 12:03:28 +00:00
if ( missing _fields . length ) {
this . $element . find ( "#result" ) . before ( '<div id="message" style="color:red">*Required Fields are not selected : ' + missing _fields + '.</div>' ) ;
2011-09-22 12:08:10 +00:00
return false ;
2011-09-06 12:11:46 +00:00
}
2011-09-22 12:08:10 +00:00
return true ;
2011-09-06 12:11:46 +00:00
} ,
2011-08-03 12:32:42 +00:00
stop : function ( ) {
$ ( this . $dialog ) . remove ( ) ;
this . _super ( ) ;
2011-08-31 10:52:32 +00:00
}
2011-07-27 06:43:25 +00:00
} ) ;
2011-09-09 10:53:07 +00:00
} ;