+sub tabbed {
+  my ($self, $tabs, @slurp) = @_;
+  my %params   = _hashify(@slurp);
+  my $id       = $params{id} || 'tab_' . _tag_id();
+
+  $params{selected} *= 1;
+
+  die 'L.tabbed needs an arrayred of tabs for first argument'
+    unless ref $tabs eq 'ARRAY';
+
+  my (@header, @blocks);
+  for my $i (0..$#$tabs) {
+    my $tab = $tabs->[$i];
+
+    next if $tab eq '';
+
+    my $selected = $params{selected} == $i;
+    my $tab_id   = "__tab_id_$i";
+    push @header, $self->li_tag(
+      $self->link('', $tab->{name}, rel => $tab_id),
+        ($selected ? (class => 'selected') : ())
+    );
+    push @blocks, $self->div_tag($tab->{data},
+      id => $tab_id, class => 'tabcontent');
+  }
+
+  return '' unless @header;
+  return $self->ul_tag(
+    join('', @header), id => $id, class => 'shadetabs'
+  ) .
+  $self->div_tag(
+    join('', @blocks), class => 'tabcontentstyle'
+  ) .
+  $self->javascript(
+    qq|var $id = new ddtabcontent("$id");$id.setpersist(true);| .
+    qq|$id.setselectedClassTarget("link");$id.init();|
+  );
+}
+
+sub tab {
+  my ($self, $name, $src, @slurp) = @_;
+  my %params = _hashify(@slurp);
+
+  $params{method} ||= 'process';
+
+  return () if defined $params{if} && !$params{if};
+
+  my $data;
+  if ($params{method} eq 'raw') {
+    $data = $src;
+  } elsif ($params{method} eq 'process') {
+    $data = $self->_context->process($src, %{ $params{args} || {} });
+  } else {
+    die "unknown tag method '$params{method}'";
+  }
+
+  return () unless $data;
+
+  return +{ name => $name, data => $data };
+}
+
+sub areainput_tag {
+  my ($self, $name, $value, @slurp) = @_;
+  my %attributes      = _hashify(@slurp);
+
+  my $rows = delete $attributes{rows}     || 1;
+  my $min  = delete $attributes{min_rows} || 1;
+
+  return $rows > 1
+    ? $self->textarea_tag($name, $value, %attributes, rows => max $rows, $min)
+    : $self->input_tag($name, $value, %attributes);
+}
+
+sub multiselect2side {
+  my ($self, $id, @slurp) = @_;
+  my %params              = _hashify(@slurp);
+
+  $params{labelsx}        = "\"" . _J($params{labelsx} || $::locale->text('Available')) . "\"";
+  $params{labeldx}        = "\"" . _J($params{labeldx} || $::locale->text('Selected'))  . "\"";
+  $params{moveOptions}    = 'false';
+
+  my $vars                = join(', ', map { "${_}: " . $params{$_} } keys %params);
+  my $code                = <<EOCODE;
+<script type="text/javascript">
+  \$().ready(function() {
+    \$('#${id}').multiselect2side({ ${vars} });
+  });
+</script>
+EOCODE
+
+  return $code;
+}
+
+sub online_help_tag {
+  my ($self, $tag, @slurp) = @_;
+  my %params               = _hashify(@slurp);
+  my $cc                   = $::myconfig{countrycode};
+  my $file                 = "doc/online/$cc/$tag.html";
+  my $text                 = $params{text} || $::locale->text('Help');
+
+  die 'malformed help tag' unless $tag =~ /^[a-zA-Z0-9_]+$/;
+  return unless -f $file;
+  return $self->html_tag('a', $text, href => $file, target => '_blank');
+}
+
+sub dump {
+  my $self = shift;
+  require Data::Dumper;
+  return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
+}
+