Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 2504ebe1

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

  • ID 2504ebe1552b488aa48999535963cc0e6bc3d4ae
  • Vorgänger eb47358a
  • Nachfolger a8617d4c

Part Picker

Unterschiede anzeigen:

SL/Controller/Part.pm
3 3
use strict;
4 4
use parent qw(SL::Controller::Base);
5 5

  
6
use Clone qw(clone);
6 7
use SL::DB::Part;
8
use SL::Controller::Helper::GetModels;
9
use SL::Controller::Helper::Filtered;
10
use SL::Controller::Helper::Sorted;
11
use SL::Controller::Helper::Paginated;
12
use SL::Controller::Helper::Filtered;
13
use SL::Locale::String qw(t8);
14

  
15
use Rose::Object::MakeMethods::Generic (
16
  'scalar --get_set_init' => [ qw(parts) ],
17
);
7 18

  
8 19
# safety
9 20
__PACKAGE__->run_before(sub { $::auth->assert('part_service_assembly_edit') });
10 21

  
22
__PACKAGE__->make_filtered(
23
  ONLY          => [ qw(part_picker_search part_picker_result) ],
24
  LAUNDER_TO    => 'filter',
25
);
26
__PACKAGE__->make_paginated(
27
  ONLY          => [ qw(part_picker_search part_picker_result) ],
28
);
29

  
30
__PACKAGE__->make_sorted(
31
  ONLY              => [ qw(part_picker_search part_picker_result) ],
32

  
33
  DEFAULT_BY        => 'partnumber',
34
  DEFAULT_DIR       => 1,
35

  
36
  partnumber        => t8('Partnumber'),
37
);
38

  
11 39
sub action_ajax_autocomplete {
12 40
  my ($self, %params) = @_;
13 41

  
......
26 54
  $self->render('part/ajax_autocomplete', { layout => 0, type => 'json' });
27 55
}
28 56

  
57
sub action_test_page {
58
  $::request->{layout}->add_javascripts('autocomplete_part.js');
59

  
60
  $_[0]->render('part/test_page');
61
}
62

  
63
sub action_part_picker_search {
64
  $_[0]->render('part/part_picker_search', { layout => 0 }, parts => $_[0]->parts);
65
}
66

  
67
sub action_part_picker_result {
68
  $_[0]->render('part/_part_picker_result', { layout => 0 });
69
}
70

  
71
sub init_parts {
72
  $_[0]->get_models;
73
}
29 74

  
30 75
1;
SL/DB/Manager/Part.pm
85 85
  return %qty_by_id;
86 86
}
87 87

  
88
sub _sort_spec {
89
  (
90
    default  => [ 'partnumber', 1 ],
91
    columns  => {
92
      SIMPLE => 'ALL',
93
    },
94
    nulls    => {},
95
  );
96
}
97

  
88 98
1;
89 99
__END__
90 100

  
SL/Form.pm
468 468

  
469 469
  $layout->use_javascript("$_.js") for (qw(
470 470
    jquery jquery-ui jquery.cookie jqModal jquery.checkall jquery.download
471
    common part_selection switchmenuframe
471
    common part_selection switchmenuframe autocomplete_part
472 472
  ), "jquery/ui/i18n/jquery.ui.datepicker-$::myconfig{countrycode}");
473 473

  
474 474
  $self->{favicon} ||= "favicon.ico";
SL/Presenter.pm
12 12
use SL::Presenter::EscapedText;
13 13
use SL::Presenter::Invoice;
14 14
use SL::Presenter::Order;
15
use SL::Presenter::Part;
15 16
use SL::Presenter::Project;
16 17
use SL::Presenter::Record;
17 18
use SL::Presenter::SepaExport;
SL/Presenter/Part.pm
1
package SL::Presenter::Part;
2

  
3
use strict;
4

  
5
use Exporter qw(import);
6
our @EXPORT = qw(part_picker);
7

  
8
sub part_picker {
9
  my ($self, $name, $value, %params) = @_;
10
  my $name_e    = $self->escape($name);
11

  
12
  my $ret =
13
    $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'part_autocomplete', type => 'hidden') .
14
    $self->input_tag("", delete $params{type}, id => $self->name_to_id("$name_e\_type"), type => 'hidden') .
15
    $self->input_tag("", (ref $value && $value->can('description')) ? $value->description : '', id => $self->name_to_id("$name_e\_name"), %params) .
16
    $self->input_tag("", delete $params{column}, id => $self->name_to_id("$name_e\_column"), type => 'hidden');
17

  
18
  $self->html_tag('span', $ret, class => 'part_picker');
19
}
20

  
21
1;
SL/Template/Plugin/L.pm
491 491
  return SL::Presenter->get->render('common/paginate', %template_params);
492 492
}
493 493

  
494
sub part_picker {
495
  my ($self, $name, $value, %params) = _hashify(3, @_);
496
  my $name_e    = _H($name);
497

  
498
  my $ret = $self->hidden_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'part_autocomplete') .
499
  $self->hidden_tag("", delete $params{type}, id => $self->name_to_id("$name_e\_type")) .
500
  $self->input_tag("", (ref $value && $value->can('description')) ? $value->description : '', id => $self->name_to_id("$name_e\_name"), %params) .
501
  $self->hidden_tag("", delete $params{column}, id => $self->name_to_id("$name_e\_column"));
502

  
503
  $self->html_tag('span', $ret, class => 'part_picker');
504
}
505

  
494 506
1;
495 507

  
496 508
__END__
css/kivitendo/main.css
380 380
.small-text {
381 381
  font-size: 0.75em;
382 382
}
383

  
384
.float-left {
385
  float: left;
386
}
387

  
388
.block-context {
389
  overflow: hidden;
390
}
391

  
392
.position-relative {
393
  position: relative;
394
}
395

  
396
.position-absolute {
397
  position: absolute;
398
}
399

  
400
div.part_picker_part {
401
  float:left; width: 350px;
402
  padding: 5px;
403
  margin: 5px;
404
  overflow:hidden;
405
  border: 1px;
406
  border-color: darkgray;
407
  border-style: solid;
408
  -webkit-border-radius: 4px;
409
  -moz-border-radius: 4px;
410
  border-radius: 4px;
411
  background-color: whitesmoke;
412
  cursor: pointer;
413
}
414

  
415
div.part_picker_part:hover {
416
  background-color: #CCCCCC;
417
  color: #FE5F14;
418
  border-color: gray;
419
}
css/lx-office-erp/main.css
432 432
.small-text {
433 433
  font-size: 0.75em;
434 434
}
435

  
436
.float-left {
437
  float: left;
438
}
439

  
440
.block-context {
441
  overflow: hidden;
442
}
443

  
444
.position-relative {
445
  position: relative;
446
}
447

  
448
.position-absolute {
449
  position: absolute;
450
}
451

  
452
div.part_picker_part {
453
  float:left; width: 350px;
454
  padding: 5px;
455
  margin: 5px;
456
  overflow:hidden;
457
  border: 1px;
458
  border-color: darkgray;
459
  border-style: solid;
460
  -webkit-border-radius: 2px;
461
  -moz-border-radius: 2px;
462
  border-radius: 2px;
463
  background-color: whitesmoke;
464
  cursor: pointer;
465
}
466

  
467
div.part_picker_part:hover {
468
  background-color: lightgray;
469
  border-color: gray;
470
}
js/autocomplete_part.js
1
$(function(){
2
  $('input.part_autocomplete').each(function(i,real){
3
    var $dummy  = $('#' + real.id + '_name');
4
    var $type   = $('#' + real.id + '_type');
5
    var $column = $('#' + real.id + '_column');
6
    $dummy.autocomplete({
7
      source: function(req, rsp) {
8
        $.ajax({
9
          url: 'controller.pl?action=Part/ajax_autocomplete',
10
          dataType: "json",
11
          data: {
12
            term: req.term,
13
            type: function() { return $type.val() },
14
            column: function() { return $column.val()===undefined ? '' : $column.val() },
15
            current: function() { return real.value },
16
            obsolete: 0,
17
          },
18
          success: function (data){ rsp(data) }
19
        });
20
      },
21
      limit: 20,
22
      delay: 50,
23
      select: function(event, ui) {
24
        $(real).val(ui.item.id);
25
        $dummy.val(ui.item.name);
26
      },
27
    });
28
    /*  In case users are impatient and want to skip ahead:
29
     *  Capture <enter> key events and check if it's a unique hit.
30
     *  If it is, go ahead and assume it was selected. If it wasn't don't do
31
     *  anything so that autocompletion kicks in.  For <tab> don't prevent
32
     *  propagation. It would be nice to catch it, but javascript is too stupid
33
     *  to fire a tab event later on, so we'd have to reimplement the "find
34
     *  next active element in tabindex order and focus it".
35
     */
36
    $dummy.keypress(function(event){
37
      if (event.keyCode == 13 || event.keyCode == 9) { // enter or tab or tab
38
        // if string is empty asume they want to delete
39
        if ($dummy.val() == '') {
40
          $(real).val('');
41
          return true;
42
        }
43
        $.ajax({
44
          url: 'controller.pl?action=Part/ajax_autocomplete',
45
          dataType: "json",
46
          data: {
47
            term: $dummy.val(),
48
            type: function() { return $type.val() },
49
            column: function() { return $column.val()===undefined ? '' : $column.val() },
50
            current: function() { return real.value },
51
            obsolete: 0,
52
          },
53
          success: function (data){
54
            // only one
55
            if (data.length == 1) {
56
              $(real).val(data[0].id);
57
              $dummy.val(data[0].description);
58
              if (event.keyCode == 13)
59
                $('#update_button').click();
60
            }
61
          }
62
        });
63
        if (event.keyCode == 13)
64
          return false;
65
      };
66
    });
67

  
68
    $dummy.blur(function(){
69
      if ($dummy.val() == '')
70
        $(real).val('');
71
    })
72

  
73
    // now add a picker div after the original input
74
    var pcont  = $('<span>').addClass('position-absolute');
75
    var picker = $('<div>');
76
    $dummy.after(pcont);
77
    pcont.append(picker);
78
    picker.addClass('icon16 CRM--Schnellsuche').click(function(){
79
      open_jqm_window({
80
        url: 'controller.pl',
81
        data: {
82
          action: 'Part/part_picker_search',
83
          real_id: function() { return $(real).attr('id') },
84
          'filter.all:substr::ilike': function(){ return $dummy.val() },
85
          'filter.type': function(){ return $type.val() },
86
          'column': function(){ return $column.val() },
87
          'real_id': function() { return real.id },
88
        },
89
        id: 'part_selection',
90
      });
91
      return true;
92
    });
93
  });
94
})
js/common.js
185 185
  return true;
186 186
}
187 187

  
188
function close_jqm_window(params) {
189
  params = params || { };
190
  var url = params.url;
191
  var id  = params.id ? params.id : 'jqm_popup_dialog';
192

  
193
  $('#' + id).jqmClose()();
194
}
195

  
188 196
$(document).ready(function () {
189 197
  // initialize all jQuery UI tab elements:
190 198
  $(".tabwidget").each(function(idx, element) { $(element).tabs(); });
locale/de/all
1422 1422
  'Part Notes'                  => 'Bemerkungen',
1423 1423
  'Part Number'                 => 'Artikelnummer',
1424 1424
  'Part Number missing!'        => 'Artikelnummer fehlt!',
1425
  'Part picker'                 => 'Artikelauswahl',
1425 1426
  'Partnumber'                  => 'Artikelnummer',
1426 1427
  'Partnumber must not be set to empty!' => 'Die Artikelnummer darf nicht auf leer ge&auml;ndert werden.',
1427 1428
  'Partnumber not unique!'      => 'Artikelnummer bereits vorhanden!',
templates/webpages/part/_part_picker_result.html
1
[%- USE T8 %]
2
[%- USE HTML %]
3
[%- USE L %]
4
[%- USE LxERP %]
5

  
6
[%# L.dump(SELF.parts) %]
7

  
8
[% FOREACH part = SELF.parts %]
9
  [% PROCESS part_block %]
10
[% END %]
11

  
12
[%- BLOCK part_block %]
13
<div class='part_picker_part'>
14
  <input type='hidden' class='part_picker_id' value='[% part.id %]'>
15
  <input type='hidden' class='part_picker_partnumber' value='[% part.partnumber %]'>
16
  <input type='hidden' class='part_picker_description' value='[% part.description %]'>
17
  <span style='float:left'>[% part.partnumber | html %]</span>
18
  <span style='float:right; font-weight:bold'>[% part.description | html %]</span>
19
  <div style='clear:both;'></div>
20
  [% 'Sellprice' | $T8 %]: [% part.sellprice_as_number | html %]
21
</div>
22
[%- END %]
23

  
24
<div style='clear:both'></div>
25

  
26
[% L.paginate_controls(target='#part_picker_result', selector='#part_picker_result') %]
27

  
28
<script type='text/javascript'>
29
  $('div.part_picker_part').each(function(){
30
    $(this).click(function(){
31
      var real_id = $('#part_picker_real_id').val();
32
      var $dummy  = $('#' + real_id + '_name');
33
      var $real   = $('#' + real_id);
34

  
35
      $dummy.val($(this).children('input.part_picker_description').val());
36
      $real.val($(this).children('input.part_picker_id').val());
37

  
38
      $('#part_selection').jqmClose();
39

  
40
      return true;
41
    });
42
  });
43
</script>
templates/webpages/part/part_picker_search.html
1
[%- USE HTML %]
2
[%- USE L %]
3
[%- USE LxERP %]
4
[%- USE T8 %]
5

  
6
<h1>[% 'Part picker' | $T8 %]</h1>
7
<div style='overflow:hidden'>
8

  
9
[% L.input_tag('part_picker_filter', SELF.filter.all_substr__ilike, class='part_picker_filter') %]
10
[% L.hidden_tag('part_picker_real_id', FORM.real_id) %]
11

  
12
<div style='clear:both'></div>
13
<div id='part_picker_result'></div>
14
</div>
15

  
16
<script type='text/javascript'>
17
  var timer;
18
  var update_results = function(){
19
    var $type   = $('#[% FORM.real_id %]_type');
20
    var $column = $('#[% FORM.real_id %]_column');
21
    $.ajax({
22
      url: 'controller.pl?action=Part/part_picker_result',
23
      data: {
24
        'filter.all:substr::ilike': function(){ var val = $('#part_picker_filter').val(); return val === undefined ? '' : val },
25
        'filter.type': function(){ return $type.val() },
26
        'column': function(){ return $column.val() },
27
        'real_id': [% FORM.real_id.json %],
28
      },
29
      success: function(data){ $('#part_picker_result').html(data) }
30
    });
31
  };
32
  $(function(){
33
    $('#part_picker_filter').focus();
34
    update_results();
35
  });
36
  $('#part_picker_filter').keypress(function (event){
37
    window.clearTimeout(timer);
38
    timer = window.setTimeout(update_results, 100);
39
  });
40
</script>
templates/webpages/part/test_page.html
1
[% USE L %]
2

  
3
<h1>Waren Picker Testpage</h1>
4

  
5
<br>
6
Alle: <br>
7
[% L.part_picker('part_id') %] <br>
8
Nur Waren: <br>
9
[% L.part_picker('part_id2', undef, type='part') %]<br>
10
Nur Dienstleistungen: <br>
11
[% L.part_picker('part_id3', undef, type='service') %]<br>

Auch abrufbar als: Unified diff