X-Git-Url: http://wagnertech.de/git?p=kivitendo-erp.git;a=blobdiff_plain;f=SL%2FLayout%2FActionBar.pm;fp=SL%2FLayout%2FActionBar.pm;h=5345e9e14e72e75f7a9dacdd8f119df80426cfb7;hp=0000000000000000000000000000000000000000;hb=53593baa211863fbf66540cf1bcc36c8fb37257f;hpb=deb4d2dbb676d7d6f69dfe7815d6e0cb09bd4a44 diff --git a/SL/Layout/ActionBar.pm b/SL/Layout/ActionBar.pm new file mode 100644 index 000000000..5345e9e14 --- /dev/null +++ b/SL/Layout/ActionBar.pm @@ -0,0 +1,299 @@ +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 { + my ($self) = @_; + + my $content = join '', map { $_->render } @{ $self->actions }; + return if !$content; + html_tag('div', $content, class => HTML_CLASS); +} + +sub javascripts_inline { + join '', map { $_->script } @{ $_[0]->actions }; +} + +sub static_javascripts { + 'kivi.ActionBar.js' +} + +###### interface + +sub add { + my ($self, @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; + +__END__ + +=encoding utf-8 + +=head1 NAME + +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 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. It's structured as a container for elements that can be +extended when needed. + +=head1 METHODS + +=over 4 + +=item * C + +Will be used during initialization of the layout. You should never have to +instanciate an action bar yourself. Get the current request instances from + + $::request->layout->get('actionbar') + +instead. + +=item * C + +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 + +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 + +=back + +These are meant to reduce enterprise operators (C<()x!!>) when conditionally adding lots +of elements. + +The combobox element is in itself a container and will simply expect the same +syntax in an arrayref. + +For the full list of parameters supported by the elements, see L. + + +=head1 GUIDELINES + +The current implementation follows these design guidelines: + +=over 4 + +=item * + +Don't put too many elements into the action bar. Group into comboboxes if +possible. Consider seven elements a reasonable limit. + +=item * + +If you've got an update button, put it first and bind the enter accesskey to +it. + +=item * + +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. + +=item * + +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 + +=cut