X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FPresenter%2FPart.pm;h=2f8624d74fb77c84271051156c501806d197bad5;hb=713de5ed35a8a1faea940354254c4e781631c495;hp=25d682037d5b897fd5619567b5257fee02383461;hpb=9aaadfdc06efbfb31f70c32cdf6aacf2f8a136f2;p=kivitendo-erp.git diff --git a/SL/Presenter/Part.pm b/SL/Presenter/Part.pm index 25d682037..2f8624d74 100644 --- a/SL/Presenter/Part.pm +++ b/SL/Presenter/Part.pm @@ -2,20 +2,134 @@ package SL::Presenter::Part; use strict; +use SL::DB::Part; +use SL::DB::PartClassification; +use SL::Locale::String qw(t8); +use SL::Presenter::EscapedText qw(escape is_escaped); +use SL::Presenter::Tag qw(input_tag html_tag name_to_id select_tag); + use Exporter qw(import); -our @EXPORT = qw(part_picker); +our @EXPORT_OK = qw( + part_picker part select_classification classification_abbreviation + type_abbreviation separate_abbreviation typeclass_abbreviation +); +our %EXPORT_TAGS = (ALL => \@EXPORT_OK); + +use Carp; + +sub part { + my ($part, %params) = @_; + + $params{display} ||= 'inline'; + + croak "Unknown display type '$params{display}'" unless $params{display} =~ m/^(?:inline|table-cell)$/; + + my $text = join '', ( + $params{no_link} ? '' : '', + escape($part->partnumber), + $params{no_link} ? '' : '', + ); + + is_escaped($text); +} sub part_picker { - my ($self, $name, $value, %params) = @_; - my $name_e = $self->escape($name); + my ($name, $value, %params) = @_; + + $value = SL::DB::Manager::Part->find_by(id => $value) if $value && !ref $value; + my $id = $params{id} || name_to_id($name); + + my @classes = $params{class} ? ($params{class}) : (); + push @classes, 'part_autocomplete'; my $ret = - $self->input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => 'part_autocomplete', type => 'hidden') . - $self->input_tag("", delete $params{type}, id => $self->name_to_id("$name_e\_type"), type => 'hidden') . - $self->input_tag("", (ref $value && $value->can('description')) ? $value->description : '', id => $self->name_to_id("$name_e\_name"), %params) . - $self->input_tag("", delete $params{column}, id => $self->name_to_id("$name_e\_column"), type => 'hidden'); + input_tag($name, (ref $value && $value->can('id') ? $value->id : ''), class => "@classes", type => 'hidden', id => $id, + 'data-part-picker-data' => JSON::to_json(\%params), + ) . + input_tag("", ref $value ? $value->displayable_name : '', id => "${id}_name", %params); + + $::request->layout->add_javascripts('kivi.Part.js'); + $::request->presenter->need_reinit_widgets($id); + + html_tag('span', $ret, class => 'part_picker'); +} + +sub picker { goto &part_picker } + +# +# shortcut for article type +# +sub type_abbreviation { + my ($part_type) = @_; + + return '' if !$part_type; + return $::locale->text('Assembly (typeabbreviation)') if $part_type eq 'assembly'; + return $::locale->text('Part (typeabbreviation)') if $part_type eq 'part'; + return $::locale->text('Assortment (typeabbreviation)') if $part_type eq 'assortment'; + return $::locale->text('Service (typeabbreviation)'); +} - $self->html_tag('span', $ret, class => 'part_picker'); +# +# Translations for Abbreviations: +# +# $::locale->text('None (typeabbreviation)') +# $::locale->text('Purchase (typeabbreviation)') +# $::locale->text('Sales (typeabbreviation)') +# $::locale->text('Merchandise (typeabbreviation)') +# $::locale->text('Production (typeabbreviation)') +# +# and for descriptions +# $::locale->text('Purchase') +# $::locale->text('Sales') +# $::locale->text('Merchandise') +# $::locale->text('Production') + +# +# shortcut for article type +# +sub classification_abbreviation { + my ($id) = @_; + + return '' if !$id; + + SL::DB::Manager::PartClassification->cache_all(); + my $obj = SL::DB::PartClassification->load_cached($id); + $obj && $obj->abbreviation ? t8($obj->abbreviation) : ''; +} + +sub typeclass_abbreviation { + my ($part) = @_; + return '' if !$part || !$part->isa('SL::DB::Part'); + return type_abbreviation($part->part_type) . classification_abbreviation($part->classification_id); +} + +# +# shortcut for article type +# +sub separate_abbreviation { + my ($id) = @_; + + return '' if !$id; + + SL::DB::Manager::PartClassification->cache_all(); + my $obj = SL::DB::PartClassification->load_cached($id); + $obj && $obj->abbreviation && $obj->report_separate ? t8($obj->abbreviation) : ''; +} + +# +# generate selection tag +# +sub select_classification { + my ($name, %attributes) = @_; + + $attributes{value_key} = 'id'; + $attributes{title_key} = 'description'; + + my $classification_type_filter = delete $attributes{type} // []; + + my $collection = SL::DB::Manager::PartClassification->get_all_sorted( where => $classification_type_filter ); + $_->description($::locale->text($_->description)) for @{ $collection }; + select_tag( $name, $collection, %attributes ); } 1; @@ -26,11 +140,15 @@ __END__ =head1 NAME -SL::Presenter::Part - Part lelated presenter stuff +SL::Presenter::Part - Part related presenter stuff =head1 SYNOPSIS -see L + # Create an html link for editing/opening a part/service/assembly + my $object = SL::DB::Manager::Part->get_first; + my $html = SL::Presenter->get->part($object, display => 'inline'); + +see also L =head1 DESCRIPTION @@ -38,28 +156,256 @@ see L =head1 FUNCTIONS +=over 2 + +=item C + +Returns a rendered version (actually an instance of +L) of the part object C<$object> + +C<%params> can include: + =over 4 -=item C +=item * display -All-in-one picker widget for parts. The name will be both id and name of the -resulting hidden C input field. An additional dummy input will be generated -which is used to find parts. For a detailed description of it's behaviour, see -section C. +Either C (the default) or C. At the moment both +representations are identical and produce the part's name linked +to the corresponding 'edit' action. -C can be an id or C instance. +=back -If C contains C only parts of this type will be used for autocompletion. +=back +=over 2 + +=item C + +Returns the shortcut of the classification + +=back + +=over 2 + +=item C + +Returns the shortcut of the classification if the classification has the separate flag set. + +=back + +=over 2 + +=item C + +Returns an HTML select tag with all available classifications. + +C<%params> can include: + +=over 4 + +=item * default + +The id of the selected item. + +=back + +=back + +=over 2 + +=item C + +All-in-one picker widget for parts. The name will be both id and name +of the resulting hidden C input field (but the ID can be +overwritten with C<$params{id}>). + +An additional dummy input will be generated which is used to find +parts. For a detailed description of its behaviour, see section +C. + +C<$value> can be a parts id or a C instance. + +If C<%params> contains C only parts of this type will be used +for autocompletion. You may comma separate multiple types as in +C. + +If C<%params> contains C only parts of this status will be used +for autocompletion. C can be one of the following strings: +C, C or C. C is the default if C is +not given. + +If C<%params> contains C only parts with this unit will be used +for autocompletion. You may comma separate multiple units as in +C. + +If C<%params> contains C only parts with a unit +that's convertible to unit will be used for autocompletion. + +If C<%params> contains C or C even +parts will be used for autocompletion which partnumber is a vendor partnumber +(makemodel) or a customer partnumber. + +If C<%params> contains C an alternative popup will be opened, +allowing multiple items to be selected. Note however that this requires +an additional callback C to work. +Also note that you can set C to 0 (or not set C) on +creation of the picker, but can open the alternative multi select popup +with js like this: +C<$("#pp_id").data("part_picker").o.multiple=1; $("#pp_id").data("part_picker").open_dialog()'> +where C is the dom id of the part picker. +Or you can even do it the other way round setting C to 1 on creation +and open a single selection popup with js. + +If C<%params> contains C an input field with the dom id +C will be rendered in the alternative popup. +This can be used in the callback for C to controll the +input postion for the items. + +If C<%params> contains C the alternative popup will not +show any results if there are more than C results. A warning +message is displayed in this case. Set C to 0 to disable +the limitation. The limit defaults to 100. + +Obsolete parts will by default not be displayed for selection. However they are +accepted as default values and can persist during updates. As with other +selectors though, they are not selectable once overridden. + +C will register it's javascript for inclusion in the next header +rendering. If you write a standard controller that only calls C once, it +will just work. In case the header is generated in a different render call +(multiple blocks, ajax, old C style controllers) you need to +include C yourself. + +On pressing the picker will try to commit the current selection, +resulting in one of the following events, whose corresponding callbacks can be +set in C: + +=over 4 + +=item * C + +If exactly one element matches the input, the internal id will be set to this +id, the internal state will be set to C and the C event on the +picker will be fired. Additionally, if C contains C a +special event C will be fired which is guaranteed to +contain a complete JSON representation of the part. + +After that the action C will be executed, which defaults to +clicking a button with id C for backward compatibility reasons. + +=item * C + +If more than one element matches the input, the internal state will be set to +undefined. + +After that the action C will be executed, which defaults to +opening a popup dialog for graphical interaction. + +=item * C + +If no element matches the input, the internal state will be set to undefined. + +If an action for C exists, it will be called with the picker +object and current term. The caller can then implement creation of new parts. + +=back + +=back + +=head1 PART PICKER SPECIFICATION + +The following list of design goals were applied: + +=over 4 + +=item * + +Parts should not be perceived by the user as distinct inputs of partnumber and +description but as a single object + +=item * + +Easy to use without documentation for novice users + +=item * + +Fast to use with keyboard for experienced users + +=item * + +Possible to use without any keyboard interaction for mouse (or touchscreen) +users + +=item * + +Must not leave the current page in event of ambiguity (cf. current select_item +mechanism) + +=item * + +Should be useable with hand scanners or similar alternative keyboard devices + +=item * + +Should not require a feedback/check loop in the common case + +=item * + +Should not be constrained to exact matches + +=item * + +Must be atomic + +=item * + +Action should be overridable + +=back + +The implementation consists of the following parts which will be referenced later: + +=over 4 + +=item 1 + +A hidden input (id input), used to hold the id of the selected part. The only +input that gets submitted + +=item 2 + +An input (dummy input) containing a description of the currently selected part, +also used by the user to search for parts + +=item 3 + +A jquery.autocomplete mechanism attached to the dummy field + +=item 4 + +A popup layer for both feedback and input of additional data in case of +ambiguity. + +=item 5 + +An internal status of the part picker, indicating whether id input and dummy +input are consistent. After leaving the dummy input the part picker must +place itself in a consistent status. + +=item 6 + +A clickable icon (popup trigger) attached to the dummy input, which triggers the popup layer. =back =head1 BUGS -None yet :) +None atm :) =head1 AUTHOR Sven Schöling Es.schoeling@linet-services.deE +Martin Helmling Emartin.helmling@opendynamic.deE + =cut