[REM] Remove code for redirect page on refresh click.
[odoo/odoo.git] / addons / web / static / src / js / data_import.js
1 openerp.web.data_import = function(openerp) {
2 var QWeb = openerp.web.qweb;
3 /**
4  * Safari does not deal well at all with raw JSON data being returned. As a
5  * result, we're going to cheat by using a pseudo-jsonp: instead of getting
6  * JSON data in the iframe, we're getting a ``script`` tag which consists of a
7  * function call and the returned data (the json dump).
8  *
9  * The function is an auto-generated name bound to ``window``, which calls
10  * back into the callback provided here.
11  *
12  * @param {Object} form the form element (DOM or jQuery) to use in the call
13  * @param {Object} attributes jquery.form attributes object
14  * @param {Function} callback function to call with the returned data
15  */
16 function jsonp(form, attributes, callback) {
17     attributes = attributes || {};
18     var options = {jsonp: _.uniqueId('import_callback_')};
19     window[options.jsonp] = function () {
20         delete window[options.jsonp];
21         callback.apply(null, arguments);
22     };
23     if ('data' in attributes) {
24         _.extend(attributes.data, options);
25     } else {
26         _.extend(attributes, {data: options});
27     }
28     $(form).ajaxSubmit(attributes);
29 }
30
31 openerp.web.DataImport = openerp.web.Dialog.extend({
32     template: 'ImportDataView',
33     dialog_title: "Import Data",
34     init: function(parent, dataset){
35         var self = this;
36         this._super(parent, {});
37         this.model = parent.model;
38         this.fields = [];
39         this.all_fields = [];
40         this.required_fields = null;
41
42         var convert_fields = function (root, prefix) {
43             prefix = prefix || '';
44             _(root.fields).each(function (f) {
45                 self.all_fields.push(prefix + f.name);
46                 if (f.fields) {
47                     convert_fields(f, prefix + f.id + '/');
48                 }
49             });
50         };
51         this.ready  = $.Deferred.queue().then(function () {
52             self.required_fields = _(self.fields).chain()
53                 .filter(function (field) { return field.required; })
54                 .pluck('name')
55                 .value();
56             convert_fields(self);
57             self.all_fields.sort();
58         });
59     },
60     start: function() {
61         var self = this;
62         this._super();
63         this.open({
64             modal: true,
65             width: '70%',
66             height: 'auto',
67             position: 'top',
68             buttons: [
69                 {text: "Close", click: function() { self.stop(); }},
70                 {text: "Import File", click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'}
71             ],
72             close: function(event, ui) {
73                 self.stop();
74             }
75         });
76         this.toggle_import_button(false);
77         this.$element.find('#csvfile').change(this.on_autodetect_data);
78         this.$element.find('fieldset').change(this.on_autodetect_data);
79         this.$element.find('fieldset legend').click(function() {
80             $(this).next().toggle();
81         });
82         this.ready.push(new openerp.web.DataSet(this, this.model).call(
83             'fields_get', [], function (fields) {
84                 self.graft_fields(fields);
85             }));
86     },
87     graft_fields: function (fields, parent, level) {
88         parent = parent || this;
89         level = level || 0;
90
91         var self = this;
92         _(fields).each(function (field, field_name) {
93             var f = {
94                 id: field_name,
95                 name: field_name,
96                 string: field.string,
97                 required: field.required
98             };
99
100             switch (field.type) {
101             case 'many2many':
102             case 'many2one':
103                 f.name += '/id';
104                 break;
105             case 'one2many':
106                 f.name += '/id';
107                 f.fields = [];
108                 // only fetch sub-fields to a depth of 2 levels
109                 if (level < 2) {
110                     self.ready.push(new openerp.web.DataSet(self, field.relation).call(
111                         'fields_get', [], function (fields) {
112                             self.graft_fields(fields, f, level+1);
113                     }));
114                 }
115                 break;
116             }
117             parent.fields.push(f);
118         });
119     },
120     toggle_import_button: function (newstate) {
121         this.$dialog.dialog('widget')
122                 .find('.oe-dialog-import-button')
123                 .button('option', 'disabled', !newstate);
124     },
125     do_import: function() {
126         if(!this.$element.find('#csvfile').val()) { return; }
127         var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10);
128         var with_headers = this.$element.find('#file_has_headers').prop('checked');
129         if (!lines_to_skip && with_headers) {
130             lines_to_skip = 1;
131         }
132         var indices = [], fields = [];
133         this.$element.find(".sel_fields").each(function(index, element) {
134             var val = element.value;
135             if (!val) {
136                 return;
137             }
138             indices.push(index);
139             fields.push(val);
140         });
141
142         jsonp(this.$element.find('#import_data'), {
143             url: '/web/import/import_data',
144             data: {
145                 model: this.model,
146                 meta: JSON.stringify({
147                     skip: lines_to_skip,
148                     indices: indices,
149                     fields: fields
150                 })
151             }
152         }, this.on_import_results);
153     },
154     on_autodetect_data: function() {
155         if(!this.$element.find('#csvfile').val()) { return; }
156         jsonp(this.$element.find('#import_data'), {
157             url: '/web/import/detect_data'
158         }, this.on_import_results);
159     },
160     on_import_results: function(results) {
161         this.$element.find('#result').empty();
162         var headers, result_node = this.$element.find("#result");
163
164         if (results['records']) {
165             var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10),
166                 with_headers = this.$element.find('#file_has_headers').prop('checked');
167             headers = with_headers ? results.records[0] : null;
168
169             result_node.append(QWeb.render('ImportView.result', {
170                 'headers': headers,
171                 'records': lines_to_skip ? results.records.slice(lines_to_skip)
172                           : with_headers ? results.records.slice(1)
173                           : results.records
174             }));
175         } else if (results['error']) {
176             result_node.append(QWeb.render('ImportView.error', {
177                 'error': results['error']}));
178         } else if (results['success']) {
179             if (this.widget_parent.widget_parent.active_view == "list") {
180                 this.widget_parent.reload_content();
181             }
182             this.stop();
183             return;
184         }
185
186         var self = this;
187         this.ready.then(function () {
188             var $fields = self.$element.find('.sel_fields').bind('blur', function () {
189                 if (this.value && !_(self.all_fields).contains(this.value)) {
190                     this.value = '';
191                 }
192             }).autocomplete({
193                 minLength: 0,
194                 source: self.all_fields,
195                 change: self.on_check_field_values
196             }).focus(function () {
197                 $(this).autocomplete('search');
198             });
199             // Column auto-detection
200             _(headers).each(function (header, index) {
201                 var f =_(self.fields).detect(function (field) {
202                     // TODO: levenshtein between header and field.string
203                     return field.name === header || field.string.toLowerCase() === header;
204                 });
205                 if (f) {
206                     $fields.eq(index).val(f.name);
207                 }
208             });
209             self.on_check_field_values();
210         });
211     },
212     /**
213      * Looks through all the field selections, and tries to find if two
214      * (or more) columns were matched to the same model field.
215      *
216      * Returns a map of the multiply-mapped fields to an array of offending
217      * columns (not actually columns, but the inputs containing the same field
218      * names).
219      *
220      * Also has the side-effect of marking the discovered inputs with the class
221      * ``duplicate_fld``.
222      *
223      * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs
224      */
225     find_duplicate_fields: function() {
226         // Maps values to DOM nodes, in order to discover duplicates
227         var values = {}, duplicates = {};
228         this.$element.find(".sel_fields").each(function(index, element) {
229             var value = element.value;
230             var $element = $(element).removeClass('duplicate_fld');
231             if (!value) { return; }
232
233             if (!(value in values)) {
234                 values[value] = element;
235             } else {
236                 var same_valued_field = values[value];
237                 if (value in duplicates) {
238                     duplicates[value].push(element);
239                 } else {
240                     duplicates[value] = [same_valued_field, element];
241                 }
242                 $element.add(same_valued_field).addClass('duplicate_fld');
243             }
244         });
245         return duplicates;
246     },
247     on_check_field_values: function () {
248         this.$element.find("#message, #msg").remove();
249
250         var required_valid = this.check_required();
251
252         var duplicates = this.find_duplicate_fields();
253         if (_.isEmpty(duplicates)) {
254             this.toggle_import_button(required_valid);
255         } else {
256             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'));
257             var $dupes = $('<dl>').appendTo($err);
258             _(duplicates).each(function(elements, value) {
259                 $('<dt>').text(value).appendTo($dupes);
260                 _(elements).each(function(element) {
261                     var cell = $(element).closest('td');
262                     $('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes);
263                 });
264             });
265             this.toggle_import_button(false);
266         }
267
268     },
269     check_required: function() {
270         if (!this.required_fields.length) { return true; }
271
272         var selected_fields = _(this.$element.find('.sel_fields').get()).chain()
273             .pluck('value')
274             .compact()
275             .value();
276
277         var missing_fields = _.difference(this.required_fields, selected_fields);
278         if (missing_fields.length) {
279             this.$element.find("#result").before('<div id="message" style="color:red">*Required Fields are not selected : ' + missing_fields + '.</div>');
280             return false;
281         }
282         return true;
283     },
284     stop: function() {
285         $(this.$dialog).remove();
286         this._super();
287     }
288 });
289 };