]> wagnertech.de Git - mfinanz.git/blob - js/kivi.Part.js
restart apache2 in postinst
[mfinanz.git] / js / kivi.Part.js
1 namespace('kivi.Part', function(ns) {
2   'use strict';
3
4   ns.open_history_popup = function() {
5     var id = $("#part_id").val();
6     kivi.popup_dialog({
7       url:    'controller.pl?action=Part/history&part.id=' + id,
8       dialog: { title: kivi.t8('History') },
9     });
10   };
11
12   ns.save = function() {
13     var data = $('#ic').serializeArray();
14     data.push({ name: 'action', value: 'Part/save' });
15
16     $.post("controller.pl", data, kivi.eval_json_result);
17   };
18
19   ns.use_as_new = function() {
20     var oldid = $("#part_id").val();
21     $('#ic').attr('action', 'controller.pl?action=Part/use_as_new&old_id=' + oldid);
22     $('#ic').submit();
23   };
24
25   ns.print_from_showdetail = function(part_id) {
26     var data = $('#print_options_form').serializeArray();
27     data.push({ name: 'action', value: 'Part/print_label' });
28     data.push({ name: 'part.id', value: part_id });
29   $.download("controller.pl", data);
30   };
31
32   ns.delete = function() {
33     var data = $('#ic').serializeArray();
34     data.push({ name: 'action', value: 'Part/delete' });
35
36     $.post("controller.pl", data, kivi.eval_json_result);
37   };
38
39   ns.reformat_number = function(event) {
40     $(event.target).val(kivi.format_amount(kivi.parse_amount($(event.target).val()), -2));
41   };
42
43   ns.set_tab_active_by_index = function (index) {
44     $("#ic_tabs").tabs({active: index});
45   };
46
47   ns.set_tab_active_by_name= function (name) {
48     var index = $('#ic_tabs a[href=#' + name + ']').parent().index();
49     ns.set_tab_active_by_index(index);
50   };
51
52   ns.reorder_items = function(order_by) {
53     var dir = $('#' + order_by + '_header_id a img').attr("data-sort-dir");
54     var part_type = $("#part_part_type").val();
55
56     var data;
57     if (part_type === 'assortment') {
58       $('#assortment thead a img').remove();
59       data = $('#assortment :input').serializeArray();
60     } else if ( part_type === 'assembly') {
61       $('#assembly thead a img').remove();
62       data = $('#assembly :input').serializeArray();
63     }
64
65     var src;
66     if (dir == "1") {
67       dir = "0";
68       src = "image/up.png";
69     } else {
70       dir = "1";
71       src = "image/down.png";
72     }
73
74     $('#' + order_by + '_header_id a').append('<img border=0 data-sort-dir=' + dir + ' src=' + src + ' alt="' + kivi.t8('sort items') + '">');
75
76     data.push(
77       { name: 'action',    value: 'Part/reorder_items' },
78       { name: 'order_by',  value: order_by             },
79       { name: 'part_type', value: part_type            },
80       { name: 'sort_dir',  value: dir                  }
81     );
82
83     $.post("controller.pl", data, kivi.eval_json_result);
84   };
85
86   ns.assortment_recalc = function() {
87     var data = $('#assortment :input').serializeArray();
88     data.push(
89       { name: 'action', value: 'Part/update_item_totals' },
90       { name: 'part_type', value: 'assortment'           }
91     );
92
93     $.post("controller.pl", data, kivi.eval_json_result);
94   };
95
96   ns.assembly_recalc = function() {
97     var data = $('#assembly :input').serializeArray();
98     data.push(
99       { name: 'action',    value: 'Part/update_item_totals' },
100       { name: 'part_type', value: 'assembly'                }
101     );
102
103     $.post("controller.pl", data, kivi.eval_json_result);
104   };
105
106   ns.set_assortment_sellprice = function() {
107     $("#part_sellprice_as_number").val($("#items_sellprice_sum").html());
108     // ns.set_tab_active_by_name('basic_data');
109     // $("#part_sellprice_as_number").focus();
110   };
111
112   ns.set_assortment_lsg_sellprice = function() {
113     $("#items_lsg_sellprice_sum_basic").closest('td').find('input').val($("#items_lsg_sellprice_sum").html());
114   };
115
116   ns.set_assortment_douglas_sellprice = function() {
117     $("#items_douglas_sellprice_sum_basic").closest('td').find('input').val($("#items_douglas_sellprice_sum").html());
118   };
119
120   ns.set_assortment_lastcost = function() {
121     $("#part_lastcost_as_number").val($("#items_lastcost_sum").html());
122     // ns.set_tab_active_by_name('basic_data');
123     // $("#part_lastcost_as_number").focus();
124   };
125
126   ns.set_assembly_sellprice = function() {
127     $("#part_sellprice_as_number").val($("#items_sellprice_sum").html());
128     // ns.set_tab_active_by_name('basic_data');
129     // $("#part_sellprice_as_number").focus();
130   };
131
132   ns.renumber_positions = function() {
133     var part_type = $("#part_part_type").val();
134     var rows;
135     if (part_type === 'assortment') {
136       rows = $('.assortment_item_row [name="position"]');
137     } else if ( part_type === 'assembly') {
138       rows = $('.assembly_item_row [name="position"]');
139     }
140     $(rows).each(function(idx, elt) {
141       $(elt).html(idx+1);
142       var row = $(elt).closest('tr');
143       if ( idx % 2 === 0 ) {
144         if ( row.hasClass('listrow1') ) {
145           row.removeClass('listrow1');
146           row.addClass('listrow0');
147         }
148       } else {
149         if ( row.hasClass('listrow0') ) {
150           row.removeClass('listrow0');
151           row.addClass('listrow1');
152         }
153       }
154     });
155   };
156
157   ns.delete_item_row = function(clicked) {
158     var row = $(clicked).closest('tr');
159     $(row).remove();
160     var part_type = $("#part_part_type").val();
161     ns.renumber_positions();
162     if (part_type === 'assortment') {
163       ns.assortment_recalc();
164     } else if ( part_type === 'assembly') {
165       ns.assembly_recalc();
166     }
167   };
168
169   ns.add_assortment_item = function() {
170     if ($('#assortment_picker').val() === '') return;
171
172     $('#row_table_id thead a img').remove();
173
174     var data = $('#assortment :input').serializeArray();
175     data.push(
176       { name: 'action', value: 'Part/add_assortment_item' },
177       { name: 'part.id', value: $('#part_id').val()       },
178       { name: 'part.part_type', value: 'assortment'       }
179     );
180     $('#assortment_picker').data('part_picker').clear();
181
182     $.post("controller.pl", data, kivi.eval_json_result);
183   };
184
185   ns.add_assembly_item = function() {
186     if ($('#assembly_picker').val() === '') return;
187
188     var data = $('#assembly :input').serializeArray();
189     data.push(
190       { name: 'action', value: 'Part/add_assembly_item' },
191       { name: 'part.id', value: $("#part_id").val()     },
192       { name: 'part.part_type', value: 'assembly'       }
193     );
194     $('#assembly_picker').data('part_picker').clear();
195
196     $.post("controller.pl", data, kivi.eval_json_result);
197   };
198
199   ns.set_multi_assembly_items = function(data) {
200     data.push({ name: 'part.id',        value: $('#part_id').val() });
201     data.push({ name: 'part.part_type', value: $('#part_part_type').val() });
202     $.post("controller.pl?action=Part/add_multi_assembly_items", data, kivi.eval_json_result);
203   };
204
205   ns.set_multi_assortment_items = function(data) {
206     data.push({ name: 'part.id', value: $('#part_id').val() });
207     data.push({ name: 'part.part_type', value: $('#part_part_type').val() });
208     $.post("controller.pl?action=Part/add_multi_assortment_items", data, kivi.eval_json_result);
209   };
210
211   ns.close_picker_dialogs = function() {
212     $('.part_autocomplete').each(function(_, e) {
213       var picker = $(e).data('part_picker');
214       if (picker && picker.dialog) picker.close_dialog();
215     });
216   };
217
218   ns.redisplay_items = function(data) {
219     var old_rows;
220     var part_type = $("#part_part_type").val();
221     if (part_type === 'assortment') {
222       old_rows = $('.assortment_item_row').detach();
223     } else if ( part_type === 'assembly') {
224       old_rows = $('.assembly_item_row').detach();
225     }
226     var new_rows = [];
227     $(data).each(function(idx, elt) {
228       new_rows.push(old_rows[elt.old_pos - 1]);
229     });
230     if (part_type === 'assortment') {
231       $(new_rows).appendTo($('#assortment_items'));
232     } else if ( part_type === 'assembly') {
233       $(new_rows).appendTo($('#assembly_items'));
234     }
235     ns.renumber_positions();
236   };
237
238   ns.focus_last_assortment_input = function () {
239     $("#assortment_items tr:last").find('input[type=text]').filter(':visible:first').focus();
240   };
241
242   ns.focus_last_assembly_input = function () {
243     $("#assembly_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
244   };
245
246   // makemodel
247   ns.makemodel_renumber_positions = function() {
248     $('.makemodel_row [name="position"]').each(function(idx, elt) {
249       $(elt).html(idx+1);
250     });
251   };
252
253   ns.delete_makemodel_row = function(clicked) {
254     var row = $(clicked).closest('tr');
255     $(row).remove();
256
257     ns.makemodel_renumber_positions();
258   };
259
260   ns.add_makemodel_row = function() {
261     if ($('#add_makemodel').val() === '') return;
262
263     var data = $('#makemodel_table :input').serializeArray();
264     data.push({ name: 'action', value: 'Part/add_makemodel_row' });
265     $('#add_makemodel').data('customer_vendor_picker').clear();
266
267     $.post("controller.pl", data, kivi.eval_json_result);
268   };
269
270   ns.focus_last_makemodel_input = function () {
271     $("#makemodel_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
272   };
273
274   // businessmodel
275   ns.businessmodel_renumber_positions = function() {
276     $('.businessmodel_row [name="position"]').each(function(idx, elt) {
277       $(elt).html(idx+1);
278     });
279   };
280
281   ns.delete_businessmodel_row = function(clicked) {
282     var row = $(clicked).closest('tr');
283     $(row).remove();
284
285     ns.businessmodel_renumber_positions();
286   };
287
288   ns.add_businessmodel_row = function() {
289     if ($('#add_businessmodel').val() === '') return;
290
291     var data = $('#businessmodel_table :input').serializeArray();
292     data.push({ name: 'action', value: 'Part/add_businessmodel_row' });
293
294     $.post("controller.pl", data, kivi.eval_json_result);
295   };
296
297   ns.focus_last_businessmodel_input = function () {
298     $("#businessmodel_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
299   };
300
301   // customerprice
302   ns.customerprice_renumber_positions = function() {
303     $('.customerprice_row [name="position"]').each(function(idx, elt) {
304       $(elt).html(idx+1);
305     });
306   };
307
308   ns.delete_customerprice_row = function(clicked) {
309     var row = $(clicked).closest('tr');
310     $(row).remove();
311
312     ns.customerprice_renumber_positions();
313   };
314
315   ns.add_customerprice_row = function() {
316     if ($('#add_customerprice').val() === '') return;
317
318     var data = $('#customerprice_table :input').serializeArray();
319     data.push({ name: 'action', value: 'Part/add_customerprice_row' });
320     $('#add_customerprice').data('customer_vendor_picker').clear();
321
322     $.post("controller.pl", data, kivi.eval_json_result);
323   };
324
325   ns.focus_last_customerprice_input = function () {
326     $("#customerprice_rows tr:last").find('input[type=text]').filter(':visible:first').focus();
327   };
328
329
330   ns.reload_bin_selection = function() {
331     $.post("controller.pl", { action: 'Part/warehouse_changed', warehouse_id: function(){ return $('#part_warehouse_id').val(); } },   kivi.eval_json_result);
332   };
333
334   var KEY = {
335     TAB:       9,
336     ENTER:     13,
337     SHIFT:     16,
338     CTRL:      17,
339     ALT:       18,
340     ESCAPE:    27,
341     PAGE_UP:   33,
342     PAGE_DOWN: 34,
343     LEFT:      37,
344     UP:        38,
345     RIGHT:     39,
346     DOWN:      40,
347   };
348
349   ns.Picker = function($real, options) {
350     var self = this;
351     this.o = $.extend(true, {
352       limit: 20,
353       delay: 50,
354       action: {
355         commit_none: function(){ },
356         commit_one:  function(){ $('#update_button').click(); },
357         commit_many: function(){ self.open_dialog(); }
358       },
359       multiple_limit: 100
360     }, $real.data('part-picker-data'), options);
361     this.$real              = $real;
362     this.real_id            = $real.attr('id');
363     this.last_real          = $real.val();
364     this.$dummy             = $($real.siblings()[0]);
365     this.autocomplete_open  = false;
366     this.menu_above         = false;
367     this.state              = this.STATES.PICKED;
368     this.last_dummy         = this.$dummy.val();
369     this.timer              = undefined;
370     this.dialog             = undefined;
371     this.multiple_default   = this.o.multiple;
372
373     this.init();
374   };
375
376   ns.Picker.prototype = {
377     CLASSES: {
378       PICKED:       'partpicker-picked',
379       UNDEFINED:    'partpicker-undefined',
380     },
381     ajax_data: function(term) {
382       var data = {
383         current:  this.$real.val(),
384       };
385
386       if (this.o.part_type)
387         data['filter.part_type'] = this.o.part_type.split(',');
388
389       if (this.o.status) {
390         if (this.o.status == 'active')   data['filter.obsolete'] = 0;
391         if (this.o.status == 'obsolete') data['filter.obsolete'] = 1;
392       } else
393         data['filter.obsolete'] = 0;
394
395       if (this.o.classification_id)
396         data['filter.classification_id:any'] = this.o.classification_id.replaceAll(',', ' ');
397
398       if (this.o.unit)
399         data['filter.unit'] = this.o.unit.split(',');
400
401       if (this.o.convertible_unit)
402         data['filter.unit_obj.convertible_to'] = this.o.convertible_unit;
403
404       if (this.o.order_locked)
405         data['filter.order_locked'] = 1;
406       if (this.o.not_order_locked)
407         data['filter.order_locked'] = 0;
408
409       var filter_name = 'all';
410       if (this.o.with_makemodel) {
411         filter_name = 'all_with_makemodel';
412       }
413       if (this.o.with_customer_partnumber) {
414         filter_name = 'all_with_customer_partnumber';
415       }
416       data['filter.' + filter_name + ':substr:multi::ilike'] = term;
417
418       return data;
419     },
420     set_item: function(item) {
421       var self = this;
422       if (item.id) {
423         this.$real.val(item.id);
424         // autocomplete ui has name, use the value for ajax items, which contains displayable_name
425         this.$dummy.val(item.name ? item.name : item.value);
426       } else {
427         this.$real.val('');
428         this.$dummy.val('');
429       }
430       this.state      = this.STATES.PICKED;
431       this.last_real  = this.$real.val();
432       this.last_dummy = this.$dummy.val();
433       this.$real.trigger('change');
434
435       if (this.o.fat_set_item && item.id) {
436         $.ajax({
437           url: 'controller.pl?action=Part/show.json',
438           data: { 'part.id': item.id },
439           success: function(rsp) {
440             self.$real.trigger('set_item:PartPicker', rsp);
441           },
442         });
443       } else {
444         this.$real.trigger('set_item:PartPicker', item);
445       }
446       this.annotate_state();
447     },
448     set_multi_items: function(data) {
449       this.run_action(this.o.action.set_multi_items, [ data ]);
450     },
451     make_defined_state: function() {
452       if (this.state == this.STATES.PICKED) {
453         this.annotate_state();
454         return true;
455       } else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '') {
456         this.set_item({});
457       } else {
458         this.set_item({ id: this.last_real, name: this.last_dummy });
459       }
460       this.annotate_state();
461     },
462     annotate_state: function() {
463       if (this.state == this.STATES.PICKED)
464         this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED);
465       else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '')
466         this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED);
467       else {
468         this.$dummy.addClass(this.STATES.UNDEFINED).removeClass(this.STATES.PICKED);
469       }
470     },
471     handle_changed_text: function(callbacks) {
472       var self = this;
473       $.ajax({
474         url: 'controller.pl?action=Part/ajax_autocomplete',
475         dataType: "json",
476         data: $.extend( self.ajax_data(self.$dummy.val()), { prefer_exact: 1 } ),
477         success: function (data) {
478           if (data.length == 1) {
479             self.set_item(data[0]);
480             if (callbacks && callbacks.match_one) self.run_action(callbacks.match_one, [ data[0] ]);
481           } else if (data.length > 1) {
482             self.state = self.STATES.UNDEFINED;
483             if (callbacks && callbacks.match_many) self.run_action(callbacks.match_many, [ data ]);
484           } else {
485             self.state = self.STATES.UNDEFINED;
486             if (callbacks && callbacks.match_none) self.run_action(callbacks.match_none, [ self, self.$dummy.val() ]);
487           }
488           self.annotate_state();
489         }
490       });
491     },
492     /*  In case users are impatient and want to skip ahead:
493      *  Capture <enter> key events and check if it's a unique hit.
494      *  If it is, go ahead and assume it was selected. If it wasn't don't do
495      *  anything so that autocompletion kicks in.  For <tab> don't prevent
496      *  propagation. It would be nice to catch it, but javascript is too stupid
497      *  to fire a tab event later on, so we'd have to reimplement the "find
498      *  next active element in tabindex order and focus it".
499      */
500     /* note:
501      *  event.which does not contain tab events in keypressed in firefox but will report 0
502      *  chrome does not fire keypressed at all on tab or escape
503      */
504     handle_keydown: function(event) {
505       var self = this;
506       if (event.which == KEY.ENTER || event.which == KEY.TAB) {
507         self.$dummy.autocomplete('close');
508
509         // if string is empty assume they want to delete
510         if (self.$dummy.val() === '') {
511           self.set_item({});
512           return true;
513         } else if (self.state == self.STATES.PICKED) {
514           if (self.o.action.commit_one) {
515             self.run_action(self.o.action.commit_one);
516           }
517           return true;
518         }
519         if (event.which == KEY.TAB) {
520           event.preventDefault();
521           self.handle_changed_text();
522         }
523         if (event.which == KEY.ENTER) {
524           event.preventDefault();
525           self.handle_changed_text({
526             match_none: self.o.action.commit_none,
527             match_one:  self.o.action.commit_one,
528             match_many: self.o.action.commit_many
529           });
530           return false;
531         }
532       } else if (event.which == KEY.DOWN && !self.autocomplete_open) {
533         var old_options = self.$dummy.autocomplete('option');
534         self.$dummy.autocomplete('option', 'minLength', 0);
535         self.$dummy.autocomplete('search', self.$dummy.val());
536         self.$dummy.autocomplete('option', 'minLength', old_options.minLength);
537       } else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) {
538         self.state = self.STATES.UNDEFINED;
539       }
540     },
541     open_dialog: function() {
542       if (this.o.multiple) {
543         this.o.multiple = this.multiple_default;
544         this.dialog = new ns.PickerMultiPopup(this);
545       } else {
546         this.dialog = new ns.PickerPopup(this);
547       }
548     },
549     close_dialog: function() {
550       this.dialog.close_dialog();
551       this.dialog = undefined;
552     },
553     init: function() {
554       var self = this;
555       this.$dummy.autocomplete({
556         source: function(req, rsp) {
557           $.ajax($.extend(self.o, {
558             url:      'controller.pl?action=Part/ajax_autocomplete',
559             dataType: "json",
560             data:     self.ajax_data(req.term),
561             success:  function (data){ rsp(data); }
562           }));
563         },
564         select: function(event, ui) {
565           self.set_item(ui.item);
566           if (self.o.action.commit_one) {
567             self.run_action(self.o.action.commit_one);
568           }
569         },
570         search: function(event) {
571           if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT))
572             event.preventDefault();
573         },
574         open: function() {
575           const $widget = self.$dummy.autocomplete('widget');
576           const input_bound = self.$dummy[0].getBoundingClientRect();
577
578           // safe inner viewport height without scrollbars
579           const viewport_height = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
580
581           // logic:
582           // if the widget was already open, keep position
583           // if fits above but not below: render above
584           // else: render below
585           if (!self.autocomplete_open) {
586             if (input_bound.top > $widget.height() && input_bound.bottom + $widget.height() > viewport_height) {
587               $widget.position({ my: "left bottom", at: "left top", of: self.$dummy });
588               self.menu_above = true;
589             } else {
590               $widget.position({ my: "left top", at: "left bottom", of: self.$dummy });
591               self.menu_above = false;
592             }
593           } else {
594             if (self.menu_above) {
595               $widget.position({ my: "left bottom", at: "left top", of: self.$dummy });
596             } else {
597               $widget.position({ my: "left top", at: "left bottom", of: self.$dummy });
598             }
599           }
600
601           self.autocomplete_open = true;
602         },
603         close: function() {
604           self.autocomplete_open = false;
605         }
606       });
607       this.$dummy.keydown(function(event){ self.handle_keydown(event); });
608       this.$dummy.on('paste', function(){
609         setTimeout(function() {
610           self.handle_changed_text();
611         }, 1);
612       });
613       this.$dummy.blur(function(){
614         window.clearTimeout(self.timer);
615         self.timer = window.setTimeout(function() { self.annotate_state(); }, 100);
616       });
617
618       var popup_button = $('<span>').addClass('ppp_popup_button');
619       this.$dummy.after(popup_button);
620       popup_button.click(function() { self.open_dialog(); });
621     },
622     run_action: function(code, args) {
623       if (typeof code === 'function')
624         code.apply(this, args);
625       else
626         kivi.run(code, args);
627     },
628     clear: function() {
629       this.set_item({});
630     }
631   };
632   ns.Picker.prototype.STATES = {
633     PICKED:    ns.Picker.prototype.CLASSES.PICKED,
634     UNDEFINED: ns.Picker.prototype.CLASSES.UNDEFINED
635   };
636
637   ns.PickerPopup = function(pp) {
638     this.timer = undefined;
639     this.pp    = pp;
640     this.open_dialog();
641   };
642
643   ns.PickerPopup.prototype = {
644     open_dialog: function() {
645       var self = this;
646       kivi.popup_dialog({
647         url: 'controller.pl?action=Part/part_picker_search',
648         data: $.extend({
649           real_id: self.pp.real_id,
650         }, self.pp.ajax_data(this.pp.$dummy.val())),
651         id: 'part_selection',
652         dialog: {
653           title: kivi.t8('Part picker'),
654           width: 800,
655           height: 800,
656         },
657         load: function() { self.init_search(); }
658       });
659       window.clearTimeout(this.timer);
660       return true;
661     },
662     init_search: function() {
663       var self = this;
664       $('#part_picker_filter').keypress(function(e) { self.result_timer(e); }).focus();
665       $('#no_paginate').change(function() { self.update_results(); });
666       this.update_results();
667     },
668     update_results: function() {
669       var self = this;
670       $.ajax({
671         url: 'controller.pl?action=Part/part_picker_result',
672         data: $.extend({
673           no_paginate: $('#no_paginate').prop('checked') ? 1 : 0,
674         }, self.pp.ajax_data(function(){
675           var val = $('#part_picker_filter').val();
676           return val === undefined ? '' : val;
677         })),
678         success: function(data){
679           $('#part_picker_result').html(data);
680           self.init_results();
681         }
682       });
683     },
684     init_results: function() {
685       var self = this;
686       $('div.part_picker_part').each(function(){
687         $(this).click(function(){
688           self.pp.set_item({
689             id:   $(this).children('input.part_picker_id').val(),
690             name: $(this).children('input.part_picker_description').val(),
691             classification_id: $(this).children('input.part_picker_classification_id').val(),
692             ean:  $(this).children('input.part_picker_ean').val(),
693             unit: $(this).children('input.part_picker_unit').val(),
694             partnumber:  $(this).children('input.part_picker_partnumber').val(),
695             description: $(this).children('input.part_picker_description').val(),
696           });
697           self.close_dialog();
698           self.pp.$dummy.focus();
699           return true;
700         });
701       });
702       $('#part_selection').keydown(function(e){
703         if (e.which == KEY.ESCAPE) {
704           self.close_dialog();
705           self.pp.$dummy.focus();
706         }
707       });
708     },
709     result_timer: function(event) {
710       var self = this;
711       if (!$('no_paginate').prop('checked')) {
712         if (event.keyCode == KEY.PAGE_UP) {
713           $('#part_picker_result a.paginate-prev').click();
714           return;
715         }
716         if (event.keyCode == KEY.PAGE_DOWN) {
717           $('#part_picker_result a.paginate-next').click();
718           return;
719         }
720       }
721       window.clearTimeout(this.timer);
722       if (event.which == KEY.ENTER) {
723         self.update_results();
724       } else {
725         this.timer = window.setTimeout(function() { self.update_results(); }, 100);
726       }
727     },
728     close_dialog: function() {
729       $('#part_selection').dialog('close');
730     }
731   };
732
733   ns.PickerMultiPopup = function(pp) {
734     this.pp       = pp;
735     this.callback = 'Part/add_multi_' + this.pp.o.part_type + '_items';
736     this.open_dialog();
737   };
738
739   ns.PickerMultiPopup.prototype = {
740     open_dialog: function() {
741       var self = this;
742       $('#row_table_id thead a img').remove();
743
744       kivi.popup_dialog({
745         url: 'controller.pl?action=Part/show_multi_items_dialog',
746         data: $.extend({
747           real_id: self.pp.real_id,
748           show_pos_input: self.pp.o.multiple_pos_input,
749         }, self.pp.ajax_data(this.pp.$dummy.val())),
750         id: 'jq_multi_items_dialog',
751         dialog: {
752           title: kivi.t8('Add multiple items'),
753           width:  800,
754           height: 800
755         },
756         load: function() {
757           self.init_search();
758         }
759       });
760       return true;
761     },
762     init_search: function() {
763       var self = this;
764       $('#multi_items_filter_table input, #multi_items_filter_table select').keydown(function(event) {
765         if(event.which == KEY.ENTER) {
766           event.preventDefault();
767           self.update_results();
768           return false;
769         }
770       });
771
772       $('#multi_items_filter_all_substr_multi_ilike').focus();
773       $('#multi_items_filter_button').click(function(){ self.update_results(); });
774       $('#multi_items_filter_reset').click(function(){ $("#multi_items_form").resetForm(); });
775       $('#continue_button').click(function(){ self.add_multi_items(); });
776     },
777     update_results: function() {
778       var self = this;
779       var data = $('#multi_items_form').serializeArray();
780       data.push({ name: 'type',  value: self.pp.type });
781       data.push({ name: 'limit', value: self.pp.o.multiple_limit });
782       var ppdata = self.pp.ajax_data(function(){
783         var val = $('#multi_items_filter').val();
784         return val === undefined ? '' : val;
785       });
786       $.each(Object.keys(ppdata), function() {data.push({ name: 'multi_items.' + this, value: ppdata[this]});});
787       $.ajax({
788         url: 'controller.pl?action=Part/multi_items_update_result',
789         data: data,
790         method: 'post',
791         success: function(data){
792           $('#multi_items_result').html(data);
793           self.init_results();
794           self.enable_continue();
795         }
796       });
797     },
798     set_qty_to_one: function(clicked) {
799       if ($(clicked).val() === '') {
800         $(clicked).val(kivi.format_amount(1.00, -2));
801       }
802       $(clicked).select();
803     },
804     init_results: function() {
805       var self = this;
806       $('#multi_items_all_qty').change(function(event){
807         $('.multi_items_qty').val($(event.target).val());
808       });
809       $('.multi_items_qty').click(function(){ self.set_qty_to_one(this); });
810     },
811     result_timer: function() {
812     },
813     close_dialog: function() {
814       $('#jq_multi_items_dialog').dialog('close');
815     },
816     disable_continue: function() {
817       $('#multi_items_result input, #multi_items_position').off("keydown");
818       $('#continue_button').prop('disabled', true);
819     },
820     enable_continue: function() {
821       var self = this;
822       $('#multi_items_result input, #multi_items_position').keydown(function(event) {
823         if(event.keyCode == KEY.ENTER) {
824           event.preventDefault();
825           self.add_multi_items();
826           return false;
827         }
828       });
829       $('#continue_button').prop('disabled', false);
830     },
831     add_multi_items: function() {
832       // rows at all
833       var n_rows = $('.multi_items_qty').length;
834       if ( n_rows === 0) { return; }
835
836       // filled rows
837       n_rows = $('.multi_items_qty').filter(function() {
838         return $(this).val().length > 0;
839       }).length;
840       if (n_rows === 0) { return; }
841
842       this.disable_continue();
843
844       var data = $('#multi_items_form').serializeArray();
845       this.pp.set_multi_items(data);
846     }
847   };
848
849   ns.reinit_widgets = function() {
850     kivi.run_once_for('input.part_autocomplete', 'part_picker', function(elt) {
851       if (!$(elt).data('part_picker'))
852         $(elt).data('part_picker', new kivi.Part.Picker($(elt)));
853     });
854
855     kivi.run_once_for('#customerprice_rows', 'customerprice_row_sort_renumber', function(elt) {
856       $(elt).on('sortstop', kivi.Part.customerprice_renumber_positions);
857     });
858
859     kivi.run_once_for('#makemodel_rows', 'makemodel_row_sort_renumber', function(elt) {
860       $(elt).on('sortstop', kivi.Part.makemodel_renumber_positions);
861     });
862
863     kivi.run_once_for('#businessmodel_rows', 'businessmodel_row_sort_renumber', function(elt) {
864       $(elt).on('sortstop', kivi.Part.businessmodel_renumber_positions);
865     });
866   };
867
868   ns.init = function() {
869     ns.reinit_widgets();
870   };
871
872   ns.add_to_basket = function() {
873     var data = $('#ic').serializeArray();
874     data.push({ name: 'action', value: 'Part/add_to_basket' });
875
876     $.post("controller.pl", data, kivi.eval_json_result);
877   };
878
879   $(function(){
880     $('#ic').on('focusout', '.reformat_number', function(event) {
881       ns.reformat_number(event);
882     });
883
884     $('#part_warehouse_id').change(kivi.Part.reload_bin_selection);
885
886     ns.init();
887   });
888 });