Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision f16c5520

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

  • ID f16c552035ab973a9aed4a1dc29d0e16be7ff541
  • Vorgänger a4e4f1a7
  • Nachfolger e42bd22a

CustomerVendor: Picker nach Art von PartPicker

- reinit_widgets fähig
- Tab und Enter atomar
- unterstützt onChange und set_item:CustomerVendorPicker trigger
- unterstützt fat_set_item

Unterschiede anzeigen:

SL/Controller/CustomerVendor.pm
7 7
use SL::DBUtils;
8 8
use SL::Helper::Flash;
9 9
use SL::Locale::String;
10
use SL::Controller::Helper::GetModels;
10 11

  
11 12
use SL::DB::Customer;
12 13
use SL::DB::Vendor;
......
23 24
use SL::DB::History;
24 25
use SL::DB::Currency;
25 26

  
27
use Rose::Object::MakeMethods::Generic (
28
  'scalar --get_set_init' => [ qw(customer_models vendor_models) ],
29
);
30

  
26 31
# safety
27 32
__PACKAGE__->run_before(
28 33
  sub {
......
50 55
  '_load_customer_vendor',
51 56
  only => [
52 57
    'edit',
58
    'show',
53 59
    'update',
54 60
    'ajaj_get_shipto',
55 61
    'ajaj_get_contact',
......
88 94
  );
89 95
}
90 96

  
97
sub action_show {
98
  my ($self) = @_;
99

  
100
  if ($::request->type eq 'json') {
101
    my $cv_hash;
102
    if (!$self->{cv}) {
103
      # TODO error
104
    } else {
105
      $cv_hash          = $self->{cv}->as_tree;
106
      $cv_hash->{cvars} = $self->{cv}->cvar_as_hashref;
107
    }
108

  
109
    $self->render(\ SL::JSON::to_json($cv_hash), { layout => 0, type => 'json', process => 0 });
110
  }
111
}
112

  
91 113
sub _save {
92 114
  my ($self) = @_;
93 115

  
......
539 561
  $self->render(\SL::JSON::to_json($data), { type => 'json', process => 0 });
540 562
}
541 563

  
542
sub action_ajaj_customer_autocomplete {
564
sub action_ajaj_autocomplete {
543 565
  my ($self, %params) = @_;
544 566

  
545
  my $limit = $::form->{limit} || 20;
546
  my $type  = $::form->{type}  || {};
547
  my $query = { ilike => '%'. $::form->{term} .'%' };
548

  
549
  my @filter;
550
  push(
551
    @filter,
552
    $::form->{column} ? ($::form->{column} => $query) : (or => [ customernumber => $query, name => $query ])
553
  );
567
  my ($model, $manager, $number, $matches);
568

  
569
  # first see if this is customer or vendor picking
570
  if ($::form->{type} eq 'customer') {
571
     $model   = $self->customer_models;
572
     $manager = 'SL::DB::Manager::Customer';
573
     $number  = 'customernumber';
574
  } elsif ($::form->{type} eq 'vendor')  {
575
     $model   = $self->vendor_models;
576
     $manager = 'SL::DB::Manager::Vendor';
577
     $number  = 'vendornumber';
578
  } else {
579
     die "unknown type $::form->{type}";
580
  }
554 581

  
555
  push @filter, (or => [ obsolete => undef, obsolete => 0 ]) if !$::form->{obsolete};
582
  # if someone types something, and hits enter, assume he entered the full name.
583
  # if something matches, treat that as sole match
584
  # unfortunately get_models can't do more than one per package atm, so we d it
585
  # the oldfashioned way.
586
  if ($::form->{prefer_exact}) {
587
    my $exact_matches;
588
    if (1 == scalar @{ $exact_matches = $manager->get_all(
589
      query => [
590
        obsolete => 0,
591
        or => [
592
          name    => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
593
          $number => { ilike => $::form->{filter}{'all:substr:multi::ilike'} },
594
        ]
595
      ],
596
      limit => 2,
597
    ) }) {
598
      $matches = $exact_matches;
599
    }
600
  }
556 601

  
557
  my $customers = SL::DB::Manager::Customer->get_all(query => [ @filter ], limit => $limit);
558
  my $value_col = $::form->{column} || 'name';
602
  $matches //= $model->get;
603

  
604
  my @hashes = map {
605
   +{
606
     value       => $_->name,
607
     label       => $_->displayable_name,
608
     id          => $_->id,
609
     $number     => $_->$number,
610
     name        => $_->name,
611
     type        => $::form->{type},
612
     cvars       => { map { ($_->config->name => { value => $_->value_as_text, is_valid => $_->is_valid }) } @{ $_->cvars_by_config } },
613
    }
614
  } @{ $matches };
559 615

  
560
  my $data = [
561
    map(
562
      {
563
        {
564
          value => $_->can($value_col)->($_),
565
          label => $_->displayable_name,
566
          id    => $_->id,
567
          customernumber => $_->customernumber,
568
          name  => $_->name,
569
        }
570
      }
571
      @{$customers}
572
    )
573
  ];
616
  $self->render(\ SL::JSON::to_json(\@hashes), { layout => 0, type => 'json', process => 0 });
617
}
574 618

  
575
  $self->render(\SL::JSON::to_json($data), { layout => 0, type => 'json' });
619
sub action_test_page {
620
  $::request->{layout}->add_javascripts('autocomplete_customer.js');
621
  $_[0]->render('customer_vendor/test_page');
576 622
}
577 623

  
578 624
sub is_vendor {
......
892 938
  return $address;
893 939
}
894 940

  
941
sub init_customer_models {
942
  my ($self) = @_;
943

  
944
  SL::Controller::Helper::GetModels->new(
945
    controller   => $self,
946
    model        => 'Customer',
947
    sorted => {
948
      _default  => {
949
        by => 'name',
950
        dir  => 1,
951
      },
952
      name  => t8('name'),
953
    },
954
  );
955
}
956

  
957
sub init_vendor_models {
958
  my ($self) = @_;
959

  
960
  SL::Controller::Helper::GetModels->new(
961
    controller => $self,
962
    model      => 'Vendor',
963
    sorted => {
964
      _default  => {
965
        by => 'name',
966
        dir  => 1,
967
      },
968
      name  => t8('name'),
969
    },
970
  );
971
}
972

  
895 973
1;
SL/DB/Customer.pm
2 2

  
3 3
use strict;
4 4

  
5
use Rose::DB::Object::Helpers qw(as_tree);
6

  
5 7
use SL::DB::MetaSetup::Customer;
6 8
use SL::DB::Manager::Customer;
7 9
use SL::DB::Helper::TransNumberGenerator;
SL/DB/Manager/Customer.pm
3 3
use strict;
4 4

  
5 5
use SL::DB::Helper::Manager;
6
use base qw(SL::DB::Helper::Manager);
7

  
8 6
use SL::DB::Helper::Sorted;
7
use SL::DB::Helper::Paginated;
8
use SL::DB::Helper::Filtered;
9
use base qw(SL::DB::Helper::Manager);
9 10

  
10 11
sub object_class { 'SL::DB::Customer' }
11 12

  
12 13
__PACKAGE__->make_manager_methods;
13 14

  
15
__PACKAGE__->add_filter_specs(
16
  all => sub {
17
    my ($key, $value, $prefix) = @_;
18
    return or => [ map { $prefix . $_ => $value } qw(customernumber name) ]
19
  }
20
);
21

  
14 22
sub _sort_spec {
15 23
  return ( default => [ 'name', 1 ],
16 24
           columns => { SIMPLE => 'ALL',
SL/DB/Manager/Vendor.pm
3 3
use strict;
4 4

  
5 5
use SL::DB::Helper::Manager;
6
use base qw(SL::DB::Helper::Manager);
7

  
8
use SL::DB::Helper::Filtered;
9 6
use SL::DB::Helper::Sorted;
7
use SL::DB::Helper::Paginated;
8
use SL::DB::Helper::Filtered;
9
use base qw(SL::DB::Helper::Manager);
10 10

  
11 11
sub object_class { 'SL::DB::Vendor' }
12 12

  
SL/DB/Vendor.pm
2 2

  
3 3
use strict;
4 4

  
5
use Rose::DB::Object::Helpers qw(as_tree);
6

  
5 7
use SL::DB::MetaSetup::Vendor;
6 8
use SL::DB::Manager::Vendor;
7 9
use SL::DB::Helper::TransNumberGenerator;
SL/Presenter/CustomerVendor.pm
5 5
use parent qw(Exporter);
6 6

  
7 7
use Exporter qw(import);
8
our @EXPORT = qw(customer vendor);
8
our @EXPORT = qw(customer vendor customer_vendor_picker);
9 9

  
10 10
use Carp;
11 11

  
......
36 36
  return $self->escaped_text($text);
37 37
}
38 38

  
39
sub customer_vendor_picker {
40
  my ($self, $name, $value, %params) = @_;
41

  
42
  $value = SL::DB::Manager::Customer->find_by(id => $value) if $value && !ref $value;
43
  my $id = delete($params{id}) || $self->name_to_id($name);
44
  my $fat_set_item = delete $params{fat_set_item};
45

  
46
  my @classes = $params{class} ? ($params{class}) : ();
47
  push @classes, 'customer_vendor_autocomplete';
48
  push @classes, 'customer-vendor-picker-fat-set-item' if $fat_set_item;
49

  
50
  my $ret =
51
    $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id) .
52
    join('', map { $params{$_} ? $self->input_tag("", delete $params{$_}, id => "${id}_${_}", type => 'hidden') : '' } qw(type)) .
53
    $self->input_tag("", (ref $value && $value->can('name')) ? $value->name : '', id => "${id}_name", %params);
54

  
55
  $::request->presenter->need_reinit_widgets($id);
56

  
57
  $self->html_tag('span', $ret, class => 'customer_vendor_picker');
58
}
59

  
39 60
1;
40 61

  
41 62
__END__
SL/Template/Plugin/L.pm
67 67
sub truncate      { return _call_presenter('truncate',      @_); }
68 68
sub simple_format { return _call_presenter('simple_format', @_); }
69 69
sub part_picker   { return _call_presenter('part_picker',   @_); }
70
sub customer_vendor_picker   { return _call_presenter('customer_vendor_picker',   @_); }
70 71

  
71 72
sub _set_id_attribute {
72 73
  my ($attributes, $name, $unique) = @_;
......
241 242
  );
242 243
}
243 244

  
244
sub customer_picker {
245
  my ($self, $name, $value, %params) = _hashify(3, @_);
246
  my $name_e    = _H($name);
247

  
248
  $::request->{layout}->add_javascripts('autocomplete_customer.js');
249

  
250
  $self->hidden_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'customer_autocomplete') .
251
  $self->input_tag('', (ref $value && $value->can('name')) ? $value->name : '', id => $self->name_to_id("$name_e\_name"), %params);
252
}
253

  
254 245
# simple version with select_tag
255 246
sub vendor_selector {
256 247
  my ($self, $name, $value, %params) = _hashify(3, @_);
css/kivitendo/main.css
404 404
  padding-right: 16px;
405 405
}
406 406

  
407
.customer-vendor-picker-undefined,
407 408
.partpicker-undefined {
408 409
  color: red;
409 410
  font-style: italic;
css/lx-office-erp/main.css
455 455
.part_picker {
456 456
  padding-right: 16px;
457 457
}
458
.customer-vendor-picker-undefined,
458 459
.partpicker-undefined {
459 460
  color: red;
460 461
  font-style: italic;
js/autocomplete_customer.js
1
$(function(){
2
  $('input.customer_autocomplete').each(function(i,real){
3
    var dummy = $('#' + real.id + '_name');
4
    $(dummy).autocomplete({
5
      source: function(req, rsp) {
1
namespace('kivi', function(k){
2
  k.CustomerVendorPicker = function($real, options) {
3
    // short circuit in case someone double inits us
4
    if ($real.data("customer_vendor_picker"))
5
      return $real.data("customer_vendor_picker");
6

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

  
39
    function ajax_data(term) {
40
      var data = {
41
        'filter.all:substr:multi::ilike': term,
42
        'filter.obsolete': 0,
43
        current:  $real.val(),
44
        type:     $type.val(),
45
      };
46

  
47
      return data;
48
    }
49

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

  
65
      if (o.fat_set_item && item.id) {
6 66
        $.ajax({
7
          url: 'controller.pl?action=CustomerVendor/ajaj_customer_autocomplete',
8
          dataType: "json",
9
          data: {
10
            term: req.term,
11
            current: function() { real.val },
12
            obsolete: 0,
67
          url: 'controller.pl?action=CustomerVendor/show.json',
68
          data: { id: item.id, db: item.type },
69
          success: function(rsp) {
70
            $real.trigger('set_item:CustomerVendorPicker', rsp);
13 71
          },
14
          success: function (data){ rsp(data) }
15 72
        });
73
      } else {
74
        $real.trigger('set_item:CustomerVendorPicker', item);
75
      }
76
      annotate_state();
77
    }
78

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

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

  
103
    $dummy.autocomplete({
104
      source: function(req, rsp) {
105
        $.ajax($.extend(o, {
106
          url:      'controller.pl?action=CustomerVendor/ajaj_autocomplete',
107
          dataType: "json",
108
          data:     ajax_data(req.term),
109
          success:  function (data){ rsp(data) }
110
        }));
16 111
      },
17
      limit: 20,
18
      delay: 50,
19 112
      select: function(event, ui) {
20
        $(real).val(ui.item.id);
21
        $(dummy).val(ui.item.name);
113
        set_item(ui.item);
22 114
      },
23 115
    });
24
  });
116
    /*  In case users are impatient and want to skip ahead:
117
     *  Capture <enter> key events and check if it's a unique hit.
118
     *  If it is, go ahead and assume it was selected. If it wasn't don't do
119
     *  anything so that autocompletion kicks in.  For <tab> don't prevent
120
     *  propagation. It would be nice to catch it, but javascript is too stupid
121
     *  to fire a tab event later on, so we'd have to reimplement the "find
122
     *  next active element in tabindex order and focus it".
123
     */
124
    /* note:
125
     *  event.which does not contain tab events in keypressed in firefox but will report 0
126
     *  chrome does not fire keypressed at all on tab or escape
127
     */
128
    $dummy.keydown(function(event){
129
      if (event.which == KEY.ENTER || event.which == KEY.TAB) {
130
        // if string is empty assume they want to delete
131
        if ($dummy.val() == '') {
132
          set_item({});
133
          return true;
134
        } else if (state == STATES.PICKED) {
135
          return true;
136
        }
137
        if (event.which == KEY.TAB) event.preventDefault();
138
        $.ajax({
139
          url: 'controller.pl?action=CustomerVendor/ajaj_autocomplete',
140
          dataType: "json",
141
          data: $.extend( ajax_data($dummy.val()), { prefer_exact: 1 } ),
142
          success: function (data) {
143
            if (data.length == 1) {
144
              set_item(data[0]);
145
              if (event.which == KEY.ENTER)
146
                $('#update_button').click();
147
            } else {
148
            }
149
            annotate_state();
150
          }
151
        });
152
        if (event.which == KEY.ENTER)
153
          return false;
154
      } else {
155
        state = STATES.UNDEFINED;
156
      }
157
    });
158

  
159
    $dummy.blur(function(){
160
      window.clearTimeout(timer);
161
      timer = window.setTimeout(annotate_state, 100);
162
    });
163

  
164
    // now add a picker div after the original input
165
    var pp = {
166
      real:           function() { return $real },
167
      dummy:          function() { return $dummy },
168
      type:           function() { return $type },
169
      set_item:       set_item,
170
      reset:          make_defined_state,
171
      is_defined_state: function() { return state == STATES.PICKED },
172
    }
173
    $real.data('customer_vendor_picker', pp);
174
    return pp;
175
  }
176
});
177

  
178
$(function(){
179
  $('input.customer_vendor_autocomplete').each(function(i,real){
180
    kivi.CustomerVendorPicker($(real));
181
  })
25 182
});
js/kivi.js
127 127
        kivi.PartPicker($(elt));
128 128
      });
129 129

  
130
    if (ns.CustomerVendorPicker)
131
      ns.run_once_for('input.customer_vendor_autocomplete', 'customer_vendor_picker', function(elt) {
132
        kivi.CustomerVendorPicker($(elt));
133
      });
134

  
130 135
    var func = kivi.get_function_by_name('local_reinit_widgets');
131 136
    if (func)
132 137
      func();
templates/webpages/customer_vendor/test_page.html
1
[% USE L %]
2

  
3
<h1>Customer Vendor Autocomplete Testpage</h1>
4

  
5
<br>
6
Customer: with preselected id 822<br>
7
[% L.customer_vendor_picker('customer_id', 822, type='customer') %]<br>
8

  
9
<br><hr>
10
Vendor: <br>
11
[% L.customer_vendor_picker('vendor_id', '', type='vendor') %]<br>
12

  
13
<br><hr>
14
customer with fat change<br>
15
[% L.customer_vendor_picker('customer_id2', '', type='customer', fat_set_item=1) %]<br>
16
<div>id from change <span id='change1'></span></div>
17
<div>greeting from fat change <span id='change2'></span></div>
18

  
19
<br><hr>
20
fat vendor with change<br>
21
[% L.customer_vendor_picker('vendor_id2', '', type='vendor', fat_set_item=1) %]<br>
22
<div>id  from change<span id='change3'></span></div>
23
<div>greeting from fat change <span id='change4'></span></div>
24

  
25
<br><hr>
26
this one will be 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">
30
<input id="vendor3_id_name" type="text" name="" value="">
31
</span>
32

  
33
<br><hr>
34
this shouold have three '-' before and after touching:<br>
35
---[% L.customer_vendor_picker('vendor5_id', '', type='vendor') %]---
36

  
37

  
38
<script type='text/javascript'>
39
 $('#customer_id2').change(function() { $('#change1').html($('#customer_id2').val()) })
40
 $('#customer_id2').on('set_item:CustomerVendorPicker', function(e,o) { $('#change2').html(o.greeting) })
41

  
42
 $('#vendor_id2').change(function() { $('#change3').html($('#vendor_id2').val()) })
43
 $('#vendor_id2').on('set_item:CustomerVendorPicker', function(e,o) { $('#change4').html(o.greeting) })
44

  
45
window.setTimeout(function() {
46
  console.log("adding last one now!");
47
  $('#vendor3_id').addClass('customer_vendor_autocomplete');
48
  $('#vendor3').addClass('customer_vendor_picker');
49
  kivi.reinit_widgets();
50
}, 4000);
51
</script>
52

  

Auch abrufbar als: Unified diff