Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 4a663bf8

Von Tamino Steinert vor etwa 2 Monaten hinzugefügt

  • ID 4a663bf82dec36e05f9e5f7f039a83ad619b5067
  • Vorgänger 64a0ac19
  • Nachfolger 7bf16444

SL::Layout::Flash: fliegende Flash-Meldungen (portiert von Odyn)

TODOs:
- CSS für Design40 und Material anpassen
- Fehler von t/flash_migration/deprecated_calls beheben

Unterschiede anzeigen:

SL/ClientJS.pm
use Rose::Object::MakeMethods::Generic
(
scalar => [ qw() ],
'scalar --get_set_init' => [ qw(controller _actions _flash _flash_detail _no_flash_clear _error) ],
'scalar --get_set_init' => [ qw(controller _actions _error) ],
);
my %supported_methods = (
......
redirect_to => 1, # window.location.href = <TARGET>
save_file => 4, # kivi.save_file(<TARGET>, <ARGS>)
flash => 2, # kivi.display_flash(<TARGET>, <ARGS>)
flash_detail => 2, # kivi.display_flash_detail(<TARGET>, <ARGS>)
clear_flash => 2, # kivi.clear_flash(<TARGET>, <ARGS>)
# flash
flash => -2, # kivi.Flash.display_flash.apply({}, action.slice(1, action.length))
clear_flash => 0, # kivi.Flash.clear_flash()
show_flash => 0, # kivi.Flash.show()
hide_flash => 0, # kivi.Flash.hide()
reinit_widgets => 0, # kivi.reinit_widgets()
run => -1, # kivi.run(<TARGET>, <ARGS>)
run_once_for => 3, # kivi.run_once_for(<TARGET>, <ARGS>)
......
return [];
}
sub init__flash {
return {};
}
sub init__flash_detail {
return {};
}
sub init__error {
return '';
}
sub init__no_flash_clear {
return '';
}
sub to_json {
my ($self) = @_;
return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
return SL::JSON::to_json({ no_flash_clear => $self->_no_flash_clear, eval_actions => $self->_actions });
return SL::JSON::to_json({ eval_actions => $self->_actions });
}
sub to_array {
......
return $self;
}
sub flash {
my ($self, $type, @messages) = @_;
my $message = join ' ', grep { $_ } @messages;
if (!$self->_flash->{$type}) {
$self->_flash->{$type} = [ 'flash', $type, $message ];
push @{ $self->_actions }, $self->_flash->{$type};
} else {
$self->_flash->{$type}->[-1] .= ' ' . $message;
}
return $self;
}
sub flash_detail {
my ($self, $type, @messages) = @_;
my $message = join '<br>', grep { $_ } @messages;
if (!$self->_flash_detail->{$type}) {
$self->_flash_detail->{$type} = [ 'flash_detail', $type, $message ];
push @{ $self->_actions }, $self->_flash_detail->{$type};
} else {
$self->_flash_detail->{$type}->[-1] .= ' ' . $message;
}
return $self;
}
sub no_flash_clear{
my ($self) = @_;
$self->_no_flash_clear('1');
return $self;
$_[0]; # noop for compatibility
}
sub error {
......
=over 4
=item C<flash $type, $message>
Display a C<$message> in the flash of type C<$type>. Multiple calls of
C<flash> on the same C<$self> will be merged by type.
On the client side the flashes of all types will be cleared after each
successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
This clearing can be switched of by the function C<no_flash_clear>
=item C<flash_detail $type, $message>
Display a detailed message C<$message> in the flash of type C<$type>. Multiple calls of
C<flash_detail> on the same C<$self> will be merged by type.
So the flash message can be hold short and the visibility of details can toggled by the user.
=item C<no_flash_clear>
=item C<flash $type, $message [, $details [, timestamp ]]>
No automatic clearing of flash after successful ClientJS call
Display a C<$message> in the flash of type C<$type> with optional
C<$details>.
=item C<error $message>
SL/Controller/Flash.pm
package SL::Controller::Flash;
use strict;
use parent qw(SL::Controller::Base);
use SL::Helper::Flash;
sub action_reload {
my ($self) = @_;
$self->js->clear_flash;
$self->js->flash(@$_) for SL::Helper::Flash::flash_contents;
$self->js->show_flash;
$self->js->render;
}
1;
__END__
=encoding utf-8
=head1 NAME
SL::Controller::Flash - Flash actions
=head1 DESCRIPTION
This controller contains actions that can be used to reload and control client
side flash messages
=head1 BUGS
None yet :)
=head1 AUTHOR
Sven Schöling E<lt>sven.schoeling@opendynamic.deE<gt>
=cut
SL/Helper/Flash.pm
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(flash flash_later);
our @EXPORT_OK = qw(render_flash delay_flash);
our @EXPORT_OK = qw(delay_flash);
my %valid_categories = (
map({$_ => 'info'} qw(information message)),
......
$::auth->set_session_value({ key => "FLASH", value => _store_flash($::auth->get_session_value('FLASH'), @_), auto_restore => 1 });
}
sub delay_flash {
my $store = $::form->{FLASH} || { };
flash_later($_ => @{ $store->{$_} || [] }) for keys %$store;
sub flash_contents {
return unless $::form;
return unless $::form->{FLASH};
return unless 'ARRAY' eq ref $::form->{FLASH};
@{ $::form->{FLASH} }
}
sub render_flash {
return $::form->parse_html_template('common/flash');
sub delay_flash {
my $store = $::form->{FLASH} || [];
flash_later(@{ $_ || [] }) for @$store;
}
#
......
#
sub _store_flash {
my $store = shift || { };
my $category = _check_category(+shift);
my ($store, $type, $message, $details, $timestamp) = @_;
$store //= [ ];
$timestamp //= time();
my $category = _check_category($type);
$store->{ $category } ||= [ ];
push @{ $store->{ $category } }, @_;
push @{ $store }, [ $type, $message, $details, $timestamp ];
return $store;
}
......
=head1 SYNOPSIS
use SL::Helper::Flash qw(flash flash_later delay_flash);
# display in this request
flash('info', 'Customer saved!');
flash('error', 'Something went wrong', "details about what went wrong");
flash('warning', 'this might not be a good idea');
# display after a redirect
flash_later('info', 'Customer saved!');
flash_later('error', 'Something went wrong', "details about what went wrong");
flash_later('warning', 'this might not be a good idea');
# delay flash() calls to next request:
delay_flash();
=head1 DESCRIPTION
The flash is a store for messages that should be displayed to the
user. Each message has a category which is usually C<information>,
C<warning> or C<error>. The messages in each category are grouped and
displayed in colors appropriate for their severity (e.g. errors in
red).
Messages are rendered either by calling the function C<render_flash>
or by including the flash sub-template from a template with the
following code:
[%- INCLUDE 'common/flash.html' %]
Messages are rendered by including the L<SL::Layout::Flash> sub layout.
=head1 EXPORTS
The functions L</flash> and L</flash_later> are always exported.
The function L</render_flash> is only exported upon request.
=head1 FUNCTIONS
=over 4
=item C<flash $category, @messages>
=item C<flash $category, $message [, $details ]>
Stores messages for the given category. The category can be either
C<information>, C<warning> or C<error>. C<info> can also be used as an
alias for C<information>.
Store a message with optional details for the given category. The category can
be either C<information>, C<warning> or C<error>. C<info> can also be used as
an alias for C<information>.
=item C<flash_later $category, @messages>
=item C<flash_later $category, $message [, $details ]>
Stores messages for the given category for the next request. The
category can be either C<information>, C<warning> or C<error>. C<info>
can also be used as an alias for C<information>.
Store a message with optional details for the given category for the next
request. The category can be either C<information>, C<warning> or C<error>.
C<info> can also be used as an alias for C<information>.
The messages are stored in the user's session and restored upon the
The message is stored in the user's session and restored upon the
next request. Can be used for transmitting information over HTTP
redirects.
=item C<render_flash>
Outputs the flash message by parsing the C<common/flash.html> template
file.
This function is not exported by default.
=item C<delay_flash>
Delays flash, as if all flash messages in this request would have been
......
Not exported by default.
=item C<flash_contents>
The contents of the current flash accumulator.
=back
=head1 AUTHOR
SL/Layout/Admin.pm
use SL::Layout::None;
use SL::Layout::Top;
use SL::Layout::CssMenu;
use SL::Layout::Flash;
sub init_sub_layouts {
$_[0]->sub_layouts_by_name->{flash} = SL::Layout::Flash->new;
[
SL::Layout::None->new,
SL::Layout::CssMenu->new(menu => SL::Menu->new('admin')),
$_[0]->sub_layouts_by_name->{flash},
]
}
SL/Layout/Classic.pm
use SL::Layout::None;
use SL::Layout::Split;
use SL::Layout::ActionBar;
use SL::Layout::Flash;
use SL::Layout::Content;
sub init_sub_layouts {
$_[0]->sub_layouts_by_name->{actionbar} = SL::Layout::ActionBar->new;
$_[0]->sub_layouts_by_name->{flash} = SL::Layout::Flash->new;
[
SL::Layout::None->new,
SL::Layout::Top->new,
SL::Layout::Split->new(
left => [ SL::Layout::MenuLeft->new ],
right => [ $_[0]->sub_layouts_by_name->{actionbar}, SL::Layout::Content->new ],
right => [
$_[0]->sub_layouts_by_name->{actionbar},
$_[0]->sub_layouts_by_name->{flash},
SL::Layout::Content->new,
],
)
]
}
SL/Layout/Flash.pm
package SL::Layout::Flash;
use strict;
use parent qw(SL::Layout::Base);
use SL::Presenter::EscapedText qw(escape_js);
use SL::Helper::Flash;
sub pre_content {
'<div style="position:relative"><div id="layout_flash_container"></div></div>'
}
sub javascripts_inline {
my ($self) = @_;
my $js = '';
for (SL::Helper::Flash::flash_contents()) {
next if $_->[3] + 60 < time(); # ignore entries from more than one minute ago
$js .= defined $_->[2]
? sprintf("kivi.Flash.display_flash('%s', '%s', '%s');", map { escape_js($_) } @$_[0,1,2] )
: sprintf("kivi.Flash.display_flash('%s', '%s');", map { escape_js($_) } @$_[0,1] );
}
$js;
}
sub static_javascripts {
'kivi.Flash.js'
}
1;
SL/Layout/Javascript.pm
use SL::Layout::DHTMLMenu;
use SL::Layout::Top;
use SL::Layout::ActionBar;
use SL::Layout::Flash;
use SL::Layout::Content;
use List::Util qw(max);
......
sub init_sub_layouts {
$_[0]->sub_layouts_by_name->{actionbar} = SL::Layout::ActionBar->new;
$_[0]->sub_layouts_by_name->{flash} = SL::Layout::Flash->new;
[
SL::Layout::None->new,
SL::Layout::Top->new,
SL::Layout::DHTMLMenu->new,
$_[0]->sub_layouts_by_name->{actionbar},
$_[0]->sub_layouts_by_name->{flash},
SL::Layout::Content->new,
]
}
SL/Layout/Material.pm
use SL::Layout::None;
use SL::Layout::MaterialMenu;
use SL::Layout::MaterialStyle;
use SL::Layout::Flash;
use SL::Layout::Content;
sub get_stylesheet_for_user {
......
SL::Layout::None->new,
SL::Layout::MaterialStyle->new,
SL::Layout::MaterialMenu->new,
SL::Layout::Flash->new,
SL::Layout::Content->new,
]
}
SL/Layout/V3.pm
use SL::Layout::Top;
use SL::Layout::CssMenu;
use SL::Layout::ActionBar;
use SL::Layout::Flash;
use SL::Layout::Content;
sub init_sub_layouts {
$_[0]->sub_layouts_by_name->{actionbar} = SL::Layout::ActionBar->new;
$_[0]->sub_layouts_by_name->{flash} = SL::Layout::Flash->new;
[
SL::Layout::None->new,
SL::Layout::Top->new,
SL::Layout::CssMenu->new,
$_[0]->sub_layouts_by_name->{actionbar},
$_[0]->sub_layouts_by_name->{flash},
SL::Layout::Content->new,
]
}
css/kivitendo/main.css
font-weight: bold;
}
/* Flash message */
#layout_flash_container {
position: absolute;
z-index: 200;
right: 20px;
min-width: 30%;
animation: fadein .5s;
}
@keyframes hop {
to { transform: scale(1.01); }
}
@keyframes flash {
from { background-color: black }
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.layout-flash-message {
margin-top: 5px;
margin-bottom: 5px;
padding: 5px;
border-width: 1px;
border-style: solid;
/* animation: .5s ease-out .2s 1 normal none running flash;*/
}
.layout-flash-body{
padding-top:5px;
margin-top:5px;
border-top: 1px dotted black;
}
.layout-flash-timestamp{
opacity: 0.75;
}
.layout-flash-details::before {
content: ': ';
}
.layout-flash-type {
font-weight: bold;
float: right;
}
.layout-flash-error {
background-color: #FFD6D6;
border-color: #AE0014;
}
.layout-flash-ok {
background-color: #ADFFB6;
border-color: #007F0F;
}
.layout-flash-warning {
background-color: #FFE8C7;
border-color: #FF6600;
}
.layout-flash-info {
background-color: #DCF2FF;
border-color: #4690FF;
}
.layout-flash-title {
font-weight: bold;
}
.layout-flash-remove {
padding-right: 4px;
}
/* Admin section: the menu itself doesn't occupy space. So make room
at the top of the div covering the whole admin area. */
body > div.admin {
js/client_js.js
// SL/ClientJS.pm for instructions.
namespace("kivi", function(ns) {
ns.display_flash = function(type, message, noscroll) {
$('#flash_' + type + '_content').text(message);
$('#flash_' + type).show();
if (!noscroll && $('#frame-header')[0]) {
$('#frame-header')[0].scrollIntoView();
}
};
ns.display_flash_detail = function(type, message) {
$('#flash_' + type + '_detail').html(message);
$('#flash_' + type + '_disp').show();
};
ns.clear_flash = function(category , timeout) {
window.setTimeout(function(){
$('#flash_' + category).hide();
$('#flash_detail_' + category).hide();
$('#flash_' + category + '_disp').hide();
$('#flash_' + category + '_content').empty();
$('#flash_' + category + '_detail').empty();
}, timeout);
};
ns.eval_json_result = function(data) {
if (!data)
return;
if (data.error)
return ns.display_flash('error', data.error);
if (data.error && ns.Flash)
return ns.Flash.display_flash('error', data.error);
if (!data.no_flash_clear) {
$(['info', 'warning', 'error']).each(function(idx, category) {
$('#flash_' + category).hide();
$('#flash_detail_' + category).hide();
$('#flash_' + category + '_disp').hide();
$('#flash_' + category + '_content').empty();
$('#flash_' + category + '_detail').empty();
});
}
if ((data.js || '') !== '')
// jshint -W061
eval(data.js);
......
// ## other stuff ##
else if (action[0] == 'redirect_to') window.location.href = action[1];
else if (action[0] == 'save_file') kivi.save_file(action[1], action[2], action[3], action[4]);
else if (action[0] == 'flash') kivi.display_flash(action[1], action[2]);
else if (action[0] == 'flash_detail') kivi.display_flash_detail(action[1], action[2]);
else if (action[0] == 'clear_flash') kivi.clear_flash(action[1], action[2]);
// flash
else if (action[0] == 'flash') kivi.Flash.display_flash.apply({}, action.slice(1, action.length));
else if (action[0] == 'clear_flash') kivi.Flash.clear_flash();
else if (action[0] == 'show_flash') kivi.Flash.show();
else if (action[0] == 'hide_flash') kivi.Flash.hide();
else if (action[0] == 'reinit_widgets') kivi.reinit_widgets();
else if (action[0] == 'run') kivi.run(action[1], action.slice(2, action.length));
else if (action[0] == 'run_once_for') kivi.run_once_for(action[1], action[2], action[3]);
js/kivi.Flash.js
namespace("kivi.Flash", function(ns) {
"use strict";
ns.type_to_title = {
error: kivi.t8('Error'),
warning: kivi.t8('Warning'),
info: kivi.t8('Information'),
ok: kivi.t8('Ok')
};
ns.display_flash = function(type, message, details, timestamp) {
let $dom = $('<div>');
$dom.addClass('layout-flash-' + type);
$dom.addClass('layout-flash-message');
let $header = $('<div>');
$header.addClass('layout-flash-header');
let $remove = $('<span>✘</span>');
$remove.addClass('layout-flash-remove').addClass('cursor-pointer');
$remove.attr('alt', kivi.t8('Close Flash'));
$header.append($remove);
if (timestamp === undefined) {
timestamp = new Date();
} else if (timestamp > 0) {
timestamp = new Date(timestamp * 1000);
}
let $time = $('<span>');
$time.addClass('layout-flash-timestamp');
$time.text(kivi.format_time(timestamp));
$header.append($time);
let $type = $('<span>');
$type.addClass('layout-flash-type');
$type.text(ns.type_to_title[type]);
$header.append($type);
let $body = $('<div>');
$body.addClass('layout-flash-body');
if (message !== undefined && message !== null) {
let $message = $('<span>');
$message.addClass('layout-flash-content');
$message.html(message);
$body.append($message);
}
if (details !== undefined && details !== null) {
let $details = $('<span>');
$details.addClass('layout-flash-details');
$details.html(details);
$body.append($details);
}
$dom.append($header);
$dom.append($body);
$("#layout_flash_container").append($dom);
// fadeout after 1min
$dom.delay(60000).fadeOut('slow');
ns.show();
};
ns.display_flash_detail = function(type, message) {
$('#flash_' + type + '_disp').show();
};
ns.clear_flash = function(category, timeout) {
if (timeout === undefined) {
ns.clear_flash_now(category);
} else {
window.setTimeout(function(){
ns.clear_flash_now(category);
}, timeout);
}
};
ns.clear_flash_now = function(category) {
if (category) {
$('div.layout-flash-' + category).remove();
} else {
$('div.layout-flash-message').remove();
}
};
ns.remove_entry = function(e) {
$(e.target).closest('div.layout-flash-message').remove();
};
ns.toggle = function() {
$('#layout_flash_container').toggle();
};
ns.show = function() {
$('#layout_flash_container').show();
};
ns.hide = function() {
$('#layout_flash_container').hide();
};
ns.reload_flash = function() {
$.get("controller.pl", { action: "Flash/reload" }, kivi.eval_json_result);
};
ns.reinit_widgets = function() {
};
});
$(function() {
"use strict";
// dispatch to kivi.Flash for compatibility
kivi.display_flash = kivi.Flash.display_flash;
kivi.display_flash_detail = kivi.Flash.display_flash_detail;
kivi.empty_flash = kivi.Flash.empty_flash;
kivi.clear_flash = kivi.Flash.clear_flash;
$('.layout-flash-toggle').click(kivi.Flash.toggle);
$('#layout_flash_container').on('click', '.layout-flash-remove', kivi.Flash.remove_entry);
});
scripts/generate_client_js_actions.tpl
// SL/ClientJS.pm for instructions.
namespace("kivi", function(ns) {
ns.display_flash = function(type, message, noscroll) {
$('#flash_' + type + '_content').text(message);
$('#flash_' + type).show();
if (!noscroll) {
$('#frame-header')[0].scrollIntoView();
}
};
ns.display_flash_detail = function(type, message) {
$('#flash_' + type + '_detail').html(message);
$('#flash_' + type + '_disp').show();
};
ns.clear_flash = function(category , timeout) {
window.setTimeout(function(){
$('#flash_' + category).hide();
$('#flash_detail_' + category).hide();
$('#flash_' + category + '_disp').hide();
$('#flash_' + category + '_content').empty();
$('#flash_' + category + '_detail').empty();
}, timeout);
};
ns.eval_json_result = function(data) {
if (!data)
return;
if (data.error)
return ns.display_flash('error', data.error);
return ns.Flash.display_flash('error', data.error);
if (!data.no_flash_clear) {
$(['info', 'warning', 'error']).each(function(idx, category) {
$('#flash_' + category).hide();
$('#flash_detail_' + category).hide();
$('#flash_' + category + '_disp').hide();
$('#flash_' + category + '_content').empty();
$('#flash_' + category + '_detail').empty();
});
}
if ((data.js || '') !== '')
// jshint -W061
eval(data.js);
t/flash_migration/deprecated_calls.t
use strict;
use lib 't';
use Support::Files;
use Support::TestSetup;
use File::Spec;
use File::Slurp;
use Template;
use Template::Provider;
use Test::More;
my @deprecated_calls_perl = (
qr/render_flash/,
qr/flash_detail/,
qr{common/flash},
qr/flash (?:_later)? \( \s* ' (?: error | warning | information ) ' \s* , \s* \@ /x,
);
my @deprecated_calls_js = (
qr/kivi\.display_flash/,
qr/kivi\.clear_flash/,
);
for my $file (@Support::Files::files) {
open my $fh, '<', $file or die "can't open $file";
while (my $line = <$fh>) {
for my $re (@deprecated_calls_perl) {
if ($line =~ $re) {
ok 0, "$file contains '$&', most likely due to incomplete merge of the layout flash feature. Consult the documentation in this test script";
}
}
}
ok 1, $file;
}
for my $file (@Support::Files::javascript_files) {
next if $file eq 'js/kivi.Flash.js';
open my $fh, '<', $file or die "can't open $file";
while (my $line = <$fh>) {
for my $re (@deprecated_calls_js) {
if ($line =~ $re) {
TODO: { local $TODO = 'clean up compatibility kivi.display_flash and kivi.clear_flash';
ok 0, "$file contains '$&', most likely due to incomplete merge of the layout flash feature. Consult the documentation in this test script";
}
}
}
}
ok 1, $file;
}
done_testing();
__END__
=encoding utf-8
=head1 NAME
t/flash_migration&deprecated_calls.t
=head1 DESCRIPTION
Okay, if this script triggers, this is what needs to be done:
In Javascript:
- all javascript calls to "kivi.display_flash" and "kivi.clear_flash" need to
be redirected to "kivi.Flash"
- kivi.display_flash_details doesn't exist any more, instead details can now be
passed as a third argument to kivi.Flash.display_flash
In html:
- There is no common/flash.html template any more, since the layout handles
that now. Any attempt to render it needs to be removed.
In Perl:
- flash_detail doesn't exist any more, instead flash() and flash_later() take a
third argument for details
- render_flash doesn't exist anymore and is now handled by the layout.
- flash('error', @errrs) and similar must be called in a loop: flash('error', $_) for @errors
=cut
templates/design40_webpages/common/flash.html
[% USE HTML %]
[% USE LxERP %]
[% USE T8 %]
[% BLOCK output %]
<div id="flash_[% type %]" class="flash_message flash_message_[% type %]" [% IF !messages || !messages.size %] style="display: none" [% END %]>
<div class="icon-container">
<a href="#" onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()' class="icon-close"> &#10005;</a>
<span id="flash_[% type %]_disp" class="display" style="display: none">
<a href="#" onclick='$("#flash_detail_[% type %]").toggle();' class="button"> [% 'Details' | $T8 %] </a>
</span>
</div>
<div class="message-container">
<span class="flash_title">[% title %]:</span>
<div class="flash_notification">
<span id="flash_[% type %]_content" class="content">
[% FOREACH message = messages %] [% HTML.escape(message) %] [% UNLESS loop.last %]<br>[% END %] [% END %]
</span>
<div id="flash_detail_[% type %]" class="detail" style="display: none">
<span id="flash_[% type %]_detail"></span>
<a href="#" style="float:left" onclick='$("#flash_detail_[% type %]").hide()' class="icon-close">&#10005;</a>
</div>
</div><!-- /.flash_notification -->
</div>
</div>
[% END #BLOCK output %]
[% PROCESS output title=LxERP.t8('Error') type='error' messages = FLASH.error %]
[% PROCESS output title=LxERP.t8('Warning') type='warning' messages = FLASH.warning %]
[% PROCESS output title=LxERP.t8('Information') type='info' messages = FLASH.info %]
[% PROCESS output title=LxERP.t8('Ok') type='ok' messages = FLASH.ok %]
templates/webpages/common/flash.html
[%- USE HTML -%][%- USE LxERP %][%- USE T8 %]
[%- BLOCK output %]
<div id="flash_[% type %]" class="flash_message_[% type %]"[% IF !messages || !messages.size %] style="display: none"[% END %]>
<a href='#' style='float:right'
onclick='$("#flash_[% type %]_content").empty();$("#flash_[% type %]_detail").empty();$("#flash_[% type %]").hide()'>
<img src='image/close.png' border='0' alt='[% 'Close Flash' | $T8 %]'></a>
<span class="flash_title">[%- title %]:</span>
<span id="flash_[% type %]_content">
[% FOREACH message = messages %]
[%- HTML.escape(message) %]
[%- UNLESS loop.last %]<br>[% END %]
[%- END %]
</span>
<span id="flash_[% type %]_disp" style="display: none">
<a href='#' style='float:left' onclick='$("#flash_detail_[% type %]").toggle();'>
[[% 'Details' | $T8 %]]</a>&nbsp;&nbsp;</span>
<div id="flash_detail_[% type %]" style="display: none">
<br>
<span id="flash_[% type %]_detail"></span><br>
<a href='#' style='float:left'
onclick='$("#flash_detail_[% type %]").hide()'>
<img src='image/close.png' border='0' alt='[% 'Close Details' | $T8 %]'></a><br>
</div>
</div>
[%- END %]
[%- PROCESS output title=LxERP.t8('Error') type='error' messages = FLASH.error %]
[%- PROCESS output title=LxERP.t8('Warning') type='warning' messages = FLASH.warning %]
[%- PROCESS output title=LxERP.t8('Information') type='info' messages = FLASH.info %]
[%- PROCESS output title=LxERP.t8('Ok') type='ok' messages = FLASH.ok %]

Auch abrufbar als: Unified diff