Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 7e7aae8d

Von Sven Schöling vor mehr als 6 Jahren hinzugefügt

  • ID 7e7aae8d20f3faf45073e5b888989a0ee5fce146
  • Vorgänger 4643e22c
  • Nachfolger 62bf9a5c

CustomerVendor Picker: auf prototype Picker umgestellt analog zu Part

Unterschiede anzeigen:

SL/Controller/CustomerVendor.pm
640 640
}
641 641

  
642 642
sub action_test_page {
643
  $::request->{layout}->add_javascripts('autocomplete_customer.js');
644 643
  $_[0]->render('customer_vendor/test_page');
645 644
}
646 645

  
......
939 938

  
940 939
  $self->{template_args} ||= {};
941 940

  
942
  $::request->{layout}->add_javascripts('autocomplete_customer.js');
943 941
  $::request->{layout}->add_javascripts('kivi.CustomerVendor.js');
944 942
  $::request->{layout}->add_javascripts('kivi.File.js');
945 943

  
SL/Presenter/CustomerVendor.pm
53 53
  }
54 54

  
55 55
  my $id = delete($params{id}) || $self->name_to_id($name);
56
  my $fat_set_item = delete $params{fat_set_item};
57 56

  
58 57
  my @classes = $params{class} ? ($params{class}) : ();
59 58
  push @classes, 'customer_vendor_autocomplete';
60
  push @classes, 'customer-vendor-picker-fat-set-item' if $fat_set_item;
61 59

  
62 60
  my $ret =
63
    $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id) .
64
    join('', map { $params{$_} ? $self->input_tag("", delete $params{$_}, id => "${id}_${_}", type => 'hidden') : '' } qw(type)) .
61
    $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id,
62
      'data-customer-vendor-picker-data' => JSON::to_json(\%params),
63
    ) .
65 64
    $self->input_tag("", ref $value  ? $value->displayable_name : '', id => "${id}_name", %params);
66 65

  
67
  $::request->layout->add_javascripts('autocomplete_customer.js');
66
  $::request->layout->add_javascripts('kivi.CustomerVendor.js');
68 67
  $::request->presenter->need_reinit_widgets($id);
69 68

  
70 69
  $self->html_tag('span', $ret, class => 'customer_vendor_picker');
js/autocomplete_customer.js
1
namespace('kivi', function(k){
2
  "use strict";
3

  
4
  k.CustomerVendorPicker = function($real, options) {
5
    // short circuit in case someone double inits us
6
    if ($real.data("customer_vendor_picker"))
7
      return $real.data("customer_vendor_picker");
8

  
9
    var KEY = {
10
      ESCAPE: 27,
11
      ENTER:  13,
12
      TAB:    9,
13
      LEFT:   37,
14
      RIGHT:  39,
15
      PAGE_UP: 33,
16
      PAGE_DOWN: 34,
17
      SHIFT:     16,
18
      CTRL:      17,
19
      ALT:       18,
20
    };
21
    var CLASSES = {
22
      PICKED:       'customer-vendor-picker-picked',
23
      UNDEFINED:    'customer-vendor-picker-undefined',
24
      FAT_SET_ITEM: 'customer-vendor-picker-fat-set-item',
25
    }
26
    var o = $.extend({
27
      limit: 20,
28
      delay: 50,
29
      fat_set_item: $real.hasClass(CLASSES.FAT_SET_ITEM),
30
    }, options);
31
    var STATES = {
32
      PICKED:    CLASSES.PICKED,
33
      UNDEFINED: CLASSES.UNDEFINED
34
    }
35
    var real_id = $real.attr('id');
36
    var $dummy  = $('#' + real_id + '_name');
37
    var $type   = $('#' + real_id + '_type');
38
    var $unit   = $('#' + real_id + '_unit');
39
    var state   = STATES.PICKED;
40
    var last_real = $real.val();
41
    var last_dummy = $dummy.val();
42
    var timer;
43

  
44
    function ajax_data(term) {
45
      var data = {
46
        'filter.all:substr:multi::ilike': term,
47
        'filter.obsolete': 0,
48
        current:  $real.val(),
49
        type:     $type.val(),
50
      };
51

  
52
      return data;
53
    }
54

  
55
    function set_item (item) {
56
      if (item.id) {
57
        $real.val(item.id);
58
        // autocomplete ui has name, ajax items have description
59
        $dummy.val(item.name ? item.name : item.description);
60
      } else {
61
        $real.val('');
62
        $dummy.val('');
63
      }
64
      state = STATES.PICKED;
65
      last_real = $real.val();
66
      last_dummy = $dummy.val();
67
      $real.trigger('change');
68

  
69
      if (o.fat_set_item && item.id) {
70
        $.ajax({
71
          url: 'controller.pl?action=CustomerVendor/show.json',
72
          data: { id: item.id, db: item.type },
73
          success: function(rsp) {
74
            $real.trigger('set_item:CustomerVendorPicker', rsp);
75
          },
76
        });
77
      } else {
78
        $real.trigger('set_item:CustomerVendorPicker', item);
79
      }
80
      annotate_state();
81
    }
82

  
83
    function make_defined_state () {
84
      if (state == STATES.PICKED) {
85
        annotate_state();
86
        return true
87
      } else if (state == STATES.UNDEFINED && $dummy.val() === '')
88
        set_item({})
89
      else {
90
        set_item({ id: last_real, name: last_dummy })
91
      }
92
      annotate_state();
93
    }
94

  
95
    function annotate_state () {
96
      if (state == STATES.PICKED)
97
        $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
98
      else if (state == STATES.UNDEFINED && $dummy.val() === '')
99
        $dummy.removeClass(STATES.UNDEFINED).addClass(STATES.PICKED);
100
      else {
101
        $dummy.addClass(STATES.UNDEFINED).removeClass(STATES.PICKED);
102
      }
103
    }
104

  
105
    function handle_changed_text(callbacks) {
106
      $.ajax({
107
        url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete',
108
        dataType: "json",
109
        data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ),
110
        success: function (data) {
111
          if (data.length == 1) {
112
            set_item(data[0]);
113
            if (callbacks && callbacks.match_one) callbacks.match_one(data[0]);
114
          } else if (data.length > 1) {
115
            state = STATES.UNDEFINED;
116
            if (callbacks && callbacks.match_many) callbacks.match_many(data);
117
          } else {
118
            state = STATES.UNDEFINED;
119
            if (callbacks &&callbacks.match_none) callbacks.match_none();
120
          }
121
          annotate_state();
122
        }
123
      });
124
    }
125

  
126
    $dummy.autocomplete({
127
      source: function(req, rsp) {
128
        $.ajax($.extend(o, {
129
          url:      'controller.pl?action=CustomerVendor/ajaj_autocomplete',
130
          dataType: "json",
131
          data:     ajax_data(req.term),
132
          success:  function (data){ rsp(data) }
133
        }));
134
      },
135
      select: function(event, ui) {
136
        set_item(ui.item);
137
      },
138
      search: function(event, ui) {
139
        if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT))
140
          event.preventDefault();
141
      }
142
    });
143
    /*  In case users are impatient and want to skip ahead:
144
     *  Capture <enter> key events and check if it's a unique hit.
145
     *  If it is, go ahead and assume it was selected. If it wasn't don't do
146
     *  anything so that autocompletion kicks in.  For <tab> don't prevent
147
     *  propagation. It would be nice to catch it, but javascript is too stupid
148
     *  to fire a tab event later on, so we'd have to reimplement the "find
149
     *  next active element in tabindex order and focus it".
150
     */
151
    /* note:
152
     *  event.which does not contain tab events in keypressed in firefox but will report 0
153
     *  chrome does not fire keypressed at all on tab or escape
154
     */
155
    $dummy.keydown(function(event){
156
      if (event.which == KEY.ENTER || event.which == KEY.TAB) {
157
        // if string is empty assume they want to delete
158
        if ($dummy.val() === '') {
159
          set_item({});
160
          return true;
161
        } else if (state == STATES.PICKED) {
162
          return true;
163
        }
164
        if (event.which == KEY.TAB) {
165
          event.preventDefault();
166
          handle_changed_text();
167
        }
168
        if (event.which == KEY.ENTER) {
169
          handle_changed_text({
170
            match_one:  function(){$('#update_button').click();},
171
          });
172
          return false;
173
        }
174
      } else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) {
175
        state = STATES.UNDEFINED;
176
      }
177
    });
178

  
179
    $dummy.on('paste', function(){
180
      setTimeout(function() {
181
        handle_changed_text();
182
      }, 1);
183
    });
184

  
185
    $dummy.blur(function(){
186
      window.clearTimeout(timer);
187
      timer = window.setTimeout(annotate_state, 100);
188
    });
189

  
190
    // now add a picker div after the original input
191
    var pp = {
192
      real:           function() { return $real },
193
      dummy:          function() { return $dummy },
194
      type:           function() { return $type },
195
      set_item:       set_item,
196
      reset:          make_defined_state,
197
      is_defined_state: function() { return state == STATES.PICKED },
198
    }
199
    $real.data('customer_vendor_picker', pp);
200
    return pp;
201
  }
202
});
203

  
204
$(function(){
205
  $('input.customer_vendor_autocomplete').each(function(i,real){
206
    kivi.CustomerVendorPicker($(real));
207
  })
208
});
js/kivi.CustomerVendor.js
45 45
        $ctrl.prop('checked', cvar.value == 1 ? 'checked' : '');
46 46

  
47 47
      else if ((cvar.type == 'customer') || (cvar.type == 'vendor'))
48
        kivi.CustomerVendorPicker($ctrl).set_item({ id: cvar.id, name: cvar.value });
48
        kivi.CustomerVendor.Picker($ctrl).set_item({ id: cvar.id, name: cvar.value });
49 49

  
50 50
      else if (cvar.type == 'part')
51 51
        kivi.Part.Picker($ctrl).set_item({ id: cvar.id, name: cvar.value });
......
222 222
    event.preventDefault();
223 223
    ns.inline_report(target, event.target + '', {});
224 224
  };
225
});
226 225

  
227
function local_reinit_widgets() {
228
  $('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) {
229
    kivi.CustomerVendor.init_dial_action($(elt));
226
  var KEY = {
227
    TAB:       9,
228
    ENTER:     13,
229
    SHIFT:     16,
230
    CTRL:      17,
231
    ALT:       18,
232
    ESCAPE:    27,
233
    PAGE_UP:   33,
234
    PAGE_DOWN: 34,
235
    LEFT:      37,
236
    UP:        38,
237
    RIGHT:     39,
238
    DOWN:      40,
239
  };
240

  
241
  ns.Picker = function($real, options) {
242
    var self = this;
243
    this.o = $.extend(true, {
244
      limit: 20,
245
      delay: 50,
246
      action: {
247
        commit_none: function(){ },
248
        commit_one:  function(){ $('#update_button').click(); },
249
        commit_many: function(){ }
250
      }
251
    }, $real.data('customer-vendor-picker-data'), options);
252
    this.$real              = $real;
253
    this.real_id            = $real.attr('id');
254
    this.last_real          = $real.val();
255
    this.$dummy             = $($real.siblings()[0]);
256
    this.autocomplete_open  = false;
257
    this.state              = this.STATES.PICKED;
258
    this.last_dummy         = this.$dummy.val();
259
    this.timer              = undefined;
260

  
261
    this.init();
262
  };
263

  
264
  ns.Picker.prototype = {
265
    CLASSES: {
266
      PICKED:       'customer-vendor-picker-picked',
267
      UNDEFINED:    'customer-vendor-picker-undefined',
268
    },
269
    ajax_data: function(term) {
270
      return {
271
        'filter.all:substr:multi::ilike': term,
272
        'filter.obsolete': 0,
273
        current:  this.$real.val(),
274
        type:     this.o.type,
275
      };
276
    },
277
    set_item: function(item) {
278
      var self = this;
279
      if (item.id) {
280
        this.$real.val(item.id);
281
        // autocomplete ui has name, use the value for ajax items, which contains displayable_name
282
        this.$dummy.val(item.name ? item.name : item.value);
283
      } else {
284
        this.$real.val('');
285
        this.$dummy.val('');
286
      }
287
      this.state      = this.STATES.PICKED;
288
      this.last_real  = this.$real.val();
289
      this.last_dummy = this.$dummy.val();
290
      this.$real.trigger('change');
291

  
292
      if (this.o.fat_set_item && item.id) {
293
        $.ajax({
294
          url: 'controller.pl?action=CustomerVendor/show.json',
295
          data: { 'id': item.id, 'db': item.type },
296
          success: function(rsp) {
297
            self.$real.trigger('set_item:CustomerVendorPicker', rsp);
298
          },
299
        });
300
      } else {
301
        this.$real.trigger('set_item:CustomerVendorPicker', item);
302
      }
303
      this.annotate_state();
304
    },
305
    set_multi_items: function(data) {
306
      this.run_action(this.o.action.set_multi_items, [ data ]);
307
    },
308
    make_defined_state: function() {
309
      if (this.state == this.STATES.PICKED) {
310
        this.annotate_state();
311
        return true
312
      } else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '')
313
        this.set_item({})
314
      else {
315
        this.set_item({ id: this.last_real, name: this.last_dummy })
316
      }
317
      this.annotate_state();
318
    },
319
    annotate_state: function() {
320
      if (this.state == this.STATES.PICKED)
321
        this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED);
322
      else if (this.state == this.STATES.UNDEFINED && this.$dummy.val() === '')
323
        this.$dummy.removeClass(this.STATES.UNDEFINED).addClass(this.STATES.PICKED);
324
      else {
325
        this.$dummy.addClass(this.STATES.UNDEFINED).removeClass(this.STATES.PICKED);
326
      }
327
    },
328
    handle_changed_text: function(callbacks) {
329
      var self = this;
330
      $.ajax({
331
        url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete',
332
        dataType: "json",
333
        data: $.extend( self.ajax_data(self.$dummy.val()), { prefer_exact: 1 } ),
334
        success: function (data) {
335
          if (data.length == 1) {
336
            self.set_item(data[0]);
337
            if (callbacks && callbacks.match_one) self.run_action(callbacks.match_one, [ data[0] ]);
338
          } else if (data.length > 1) {
339
            self.state = self.STATES.UNDEFINED;
340
            if (callbacks && callbacks.match_many) self.run_action(callbacks.match_many, [ data ]);
341
          } else {
342
            self.state = self.STATES.UNDEFINED;
343
            if (callbacks && callbacks.match_none) self.run_action(callbacks.match_none, [ self, self.$dummy.val() ]);
344
          }
345
          self.annotate_state();
346
        }
347
      });
348
    },
349
    handle_keydown: function(event) {
350
      var self = this;
351
      if (event.which == KEY.ENTER || event.which == KEY.TAB) {
352
        // if string is empty assume they want to delete
353
        if (self.$dummy.val() === '') {
354
          self.set_item({});
355
          return true;
356
        } else if (self.state == self.STATES.PICKED) {
357
          if (self.o.action.commit_one) {
358
            self.run_action(self.o.action.commit_one);
359
          }
360
          return true;
361
        }
362
        if (event.which == KEY.TAB) {
363
          event.preventDefault();
364
          self.handle_changed_text();
365
        }
366
        if (event.which == KEY.ENTER) {
367
          self.handle_changed_text({
368
            match_none: self.o.action.commit_none,
369
            match_one:  self.o.action.commit_one,
370
            match_many: self.o.action.commit_many
371
          });
372
          return false;
373
        }
374
      } else if (event.which == KEY.DOWN && !self.autocomplete_open) {
375
        var old_options = self.$dummy.autocomplete('option');
376
        self.$dummy.autocomplete('option', 'minLength', 0);
377
        self.$dummy.autocomplete('search', self.$dummy.val());
378
        self.$dummy.autocomplete('option', 'minLength', old_options.minLength);
379
      } else if ((event.which != KEY.SHIFT) && (event.which != KEY.CTRL) && (event.which != KEY.ALT)) {
380
        self.state = self.STATES.UNDEFINED;
381
      }
382
    },
383
    init: function() {
384
      var self = this;
385
      this.$dummy.autocomplete({
386
        source: function(req, rsp) {
387
          $.ajax($.extend({}, self.o, {
388
            url:      'controller.pl?action=CustomerVendor/ajaj_autocomplete',
389
            dataType: "json",
390
            type:     'get',
391
            data:     self.ajax_data(req.term),
392
            success:  function (data){ rsp(data) }
393
          }));
394
        },
395
        select: function(event, ui) {
396
          self.set_item(ui.item);
397
        },
398
        search: function(event, ui) {
399
          if ((event.which == KEY.SHIFT) || (event.which == KEY.CTRL) || (event.which == KEY.ALT))
400
            event.preventDefault();
401
        },
402
        open: function() {
403
          self.autocomplete_open = true;
404
        },
405
        close: function() {
406
          self.autocomplete_open = false;
407
        }
408
      });
409
      this.$dummy.keydown(function(event){ self.handle_keydown(event) });
410
      this.$dummy.on('paste', function(){
411
        setTimeout(function() {
412
          self.handle_changed_text();
413
        }, 1);
414
      });
415
      this.$dummy.blur(function(){
416
        window.clearTimeout(self.timer);
417
        self.timer = window.setTimeout(function() { self.annotate_state() }, 100);
418
      });
419
    },
420
    run_action: function(code, args) {
421
      if (typeof code === 'function')
422
        code.apply(this, args)
423
      else
424
        kivi.run(code, args);
425
    },
426
    clear: function() {
427
      this.set_item({});
428
    }
429
  };
430
  ns.Picker.prototype.STATES = {
431
    PICKED:    ns.Picker.prototype.CLASSES.PICKED,
432
    UNDEFINED: ns.Picker.prototype.CLASSES.UNDEFINED
433
  };
434

  
435
  ns.reinit_widgets = function() {
436
    kivi.run_once_for('input.customer_vendor_autocomplete', 'customer_vendor_picker', function(elt) {
437
      if (!$(elt).data('customer_vendor_picker'))
438
        $(elt).data('customer_vendor_picker', new kivi.CustomerVendor.Picker($(elt)));
439
    });
440

  
441
    $('#cv_phone,#shipto_shiptophone,#contact_cp_phone1,#contact_cp_phone2,#contact_cp_mobile1,#contact_cp_mobile2').each(function(idx, elt) {
442
      kivi.CustomerVendor.init_dial_action($(elt));
443
    });
444
  }
445

  
446
  ns.init = function() {
447
    ns.reinit_widgets();
448
  }
449

  
450
  $(function(){
451
    ns.init();
230 452
  });
231
}
453
});
js/kivi.js
242 242
    });
243 243

  
244 244
    if (ns.Part) ns.Part.reinit_widgets();
245
    if (ns.CustomerVendor) ns.CustomerVendor.reinit_widgets();
245 246

  
246 247
    if (ns.ProjectPicker)
247 248
      ns.run_once_for('input.project_autocomplete', 'project_picker', function(elt) {
248 249
        kivi.ProjectPicker($(elt));
249 250
      });
250 251

  
251
    if (ns.CustomerVendorPicker)
252
      ns.run_once_for('input.customer_vendor_autocomplete', 'customer_vendor_picker', function(elt) {
253
        kivi.CustomerVendorPicker($(elt));
254
      });
255

  
256 252
    if (ns.ChartPicker)
257 253
      ns.run_once_for('input.chart_autocomplete', 'chart_picker', function(elt) {
258 254
        kivi.ChartPicker($(elt));
templates/webpages/customer_vendor/tabs/contacts.html
18 18
            empty_title = LxERP.t8('New contact'),
19 19
            value_key = 'cp_id',
20 20
            title_key = 'full_name',
21
            onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); local_reinit_widgets(); }});",
21
            onchange = "kivi.CustomerVendor.selectContact({onFormSet: function(){ contactsMapWidget.testInputs(); kivi.reinit_widgets(); }});",
22 22
          )
23 23
        %]
24 24
      </td>
templates/webpages/customer_vendor/tabs/shipto.html
16 16
             title_key = 'displayable_id',
17 17
             with_empty = 1,
18 18
             empty_title = LxERP.t8('New shipto'),
19
             onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); local_reinit_widgets(); }});",
19
             onchange = "kivi.CustomerVendor.selectShipto({onFormSet: function(){ shiptoMapWidget.testInputs(); kivi.reinit_widgets(); }});",
20 20
           )
21 21
        %]
22 22
      </td>
templates/webpages/customer_vendor/test_page.html
24 24

  
25 25
<br><hr>
26 26
this one will be a reinit_widget after 4s:<br>
27
<span id='vendor3' class="">
28
<input id="vendor3_id" class="" type="hidden" name="vendor3_id" value="">
29
<input id="vendor3_id_type" type="hidden" name="" value="vendor">
27
<span id='vendor3' class="customer_vendor_picker">
28
<input id="vendor3_id" class="" type="hidden" name="vendor3_id" value="" data-customer-vendor-picker-data="{&quot;type&quot;:&quot;vendor&quot;}">
30 29
<input id="vendor3_id_name" type="text" name="" value="">
31 30
</span>
32 31

  

Auch abrufbar als: Unified diff