X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FLayout%2FActionBar.pm;h=5345e9e14e72e75f7a9dacdd8f119df80426cfb7;hb=eaa42caeb03b5213754334a930b08407a0e74c53;hp=3d9e4093a5a115e6baf9077023fb7956fcf33042;hpb=ccf94c5dca76b164beeba398d4c493018a9b535b;p=kivitendo-erp.git diff --git a/SL/Layout/ActionBar.pm b/SL/Layout/ActionBar.pm index 3d9e4093a..5345e9e14 100644 --- a/SL/Layout/ActionBar.pm +++ b/SL/Layout/ActionBar.pm @@ -3,40 +3,79 @@ package SL::Layout::ActionBar; use strict; use parent qw(SL::Layout::Base); +use Carp; +use Scalar::Util qw(blessed); +use SL::Layout::ActionBar::Action; +use SL::Layout::ActionBar::ComboBox; +use SL::Layout::ActionBar::Link; +use SL::Layout::ActionBar::Separator; + +use SL::Presenter::Tag qw(html_tag); + use constant HTML_CLASS => 'layout-actionbar'; use Rose::Object::MakeMethods::Generic ( 'scalar --get_set_init' => [ qw(actions) ], ); +my %class_descriptors = ( + action => { class => 'SL::Layout::ActionBar::Action', num_params => 1, }, + combobox => { class => 'SL::Layout::ActionBar::ComboBox', num_params => 1, }, + link => { class => 'SL::Layout::ActionBar::Link', num_params => 1, }, + separator => { class => 'SL::Layout::ActionBar::Separator', num_params => 0, }, +); ###### Layout overrides sub pre_content { - $::request->presenter->html_tag('div', '', class => HTML_CLASS); -} + my ($self) = @_; -sub inline_javascript { - # data for bar + my $content = join '', map { $_->render } @{ $self->actions }; + return if !$content; + html_tag('div', $content, class => HTML_CLASS); } -sub javascripts { +sub javascripts_inline { + join '', map { $_->script } @{ $_[0]->actions }; +} +sub static_javascripts { + 'kivi.ActionBar.js' } ###### interface -sub add_actions { +sub add { my ($self, @actions) = @_; - push @{ $self->actions }, @actions; -} -sub init_actions { - [] + push @{ $self->actions }, $self->parse_actions(@actions); + + return $self->actions->[-1]; } +sub parse_actions { + my ($self_or_class, @actions) = @_; + + my @parsed; + + while (my $type = shift(@actions)) { + if (blessed($type) && $type->isa('SL::Layout::ActionBar::Action')) { + push @parsed, $type; + next; + } + + my $descriptor = $class_descriptors{lc $type} || croak("Unknown action type '${type}'"); + my @params = splice(@actions, 0, $descriptor->{num_params}); + push @parsed, $descriptor->{class}->from_params(@params); + } + return @parsed; +} + +sub init_actions { + [] +} 1; @@ -48,65 +87,211 @@ __END__ SL::Layout::ActionBar - Unified action buttons for controllers +=head1 SYNOPSIS + + # short sugared syntax: + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + action => [ + t8('Description'), + call => [ 'kivi.Javascript.function', @arguments ], + accesskey => 'enter', + disabled => $tooltip_with_reason_or_falsish, + only_if => $precomputed_condition, + not_if => $precomputed_condition, + id => 'html-element-id', + ], + combobox => [ + action => [...], + action => [...], + action => [...], + action => [...], + ], + link => [ + t8('Description'), + link => $url, + ], + 'separator', + ); + } + + # full syntax without sugar + for my $bar ($::request->layout->get('actionbar')) { + $bar->add( + (SL::Layout::ActionBar::Action->new( + text => t8('Description'), + params => { + call => [ 'kivi.Javascript.function', @arguments ], + accesskey => 'enter', + disabled => $tooltip_with_reason_or_falsish, + }, + )) x(!!$only_id && !$not_if), + SL::Layout::ActionBar::ComboBox->new( + actions => [ + SL::Layout::ActionBar::Action->new(...), + SL::Layout::ActionBar::Action->new(...), + SL::Layout::ActionBar::Action->new(...), + SL::Layout::ActionBar::Action->new(...), + ], + ), + SL::Layout::ActionBar::Link->new( + text => t8('Description'), + params => { + link => $url, + }, + ), + SL::Layout::ActionBar::Separator->new, + ); + } + =head1 CONCEPT -This is a layout block that does a unified action bar for any controller who +This is a layout block that creates an action bar for any controller who wants to use it. It's designed to be rendered above the content and to be -fixed when scrolling. +fixed when scrolling. It's structured as a container for elements that can be +extended when needed. -While it can be used as a generic widget container, it's designed to be able to -provide commonly used functionality as a short cut. These shortcuts include: +=head1 METHODS =over 4 -=item * +=item * C -Calling a controller with parameters +Will be used during initialization of the layout. You should never have to +instanciate an action bar yourself. Get the current request instances from -=item * + $::request->layout->get('actionbar') -Submitting a form with added parameters +instead. -=item * +=item * C -Arrangement utility +Add new elements to the bar. Can be instances of +L or scalar strings matching the sugar syntax +which is described further down. =back +=head1 SYNTACTIC SUGAR -=head1 METHODS +Instead of passing full objects to L, you can instead pass the arguments +to be used for instantiation to make the code easier to read. The short syntax +looks like this: + + type => [ + localized_description, + param => value, + param => value, + ... + ] + +A string type, followed by the parameters needed for that type. Type may be one of: + +=over 4 + +=item * C + +=item * C + +=item * C + +=item * C + +=back + +C will use no parameters, the other three will expect one arrayref. + +Two additional pseudo parameters are supported for those: =over 4 -=item C +=item * C + +=item * C + +=back + +These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots +of elements. -Dispatches each each argument to C +The combobox element is in itself a container and will simply expect the same +syntax in an arrayref. -=item C +For the full list of parameters supported by the elements, see L. -=item C +=head1 GUIDELINES -=item Clayout->actionbar +If you've got an update button, put it first and bind the enter accesskey to +it. -=head1 DOM MODEL +=item * -The entire block is rendered into a div with the class 'layout-actionbar'. +Put mutating actions (save, post, delete, check out, ship) before the separator +and non mutating actions (export, search, history, workflow) after the +separator. Combined actions (save and close) still mutate and go before the +separator. -=head1 ACTION WIDGETS +=item * -Each individual action must be an instance of C. +Avoid abusing the actionbar as a secondary menu. As a principle every action +should act upon the current element or topic. + +=item * + +Hide elements with C if they are known to be useless for the current +topic, but disable when they would be useful in principle but are not +applicable right now. For example C does not make sense in a creating +form, but makes still sense because the element can be deleted later. This +keeps the actionbar stable and reduces surprising elements that only appear in +rare situations. + +=item * + +Always add a tooltip when disabling an action. + +=item * + +Try to always add a default action with accesskey enter. Since the actionbar +lies outside of the main form, the usual submit on enter does not work out of +the box. + +=back + +=head1 DOM MODEL AND IMPLEMENTATION DETAILS + +The entire block is rendered into a div with the class 'layout-actionbar'. Each +action will render itself and will get added to the div. To keep the DOM small +and reduce startup overhead, the presentation is pure CSS and only the sticky +expansion of comboboxes is done with javascript. + +To keep startup times and HTML parsing fast the action data is simply written +into the data elements of the actions and handlers are added in a ready hook. =head1 BUGS none yet. :) +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, + =head1 AUTHOR Sven Schoeling Es.schoeling@linet-services.deE