Web-Components

 view release on metacpan or  search on metacpan

lib/Web/Components/Navigation.pm  view on Meta::CPAN

configure the JS message collection and display code. Attributes are;

=over 3

=item buffer-limit

Maximum number of messages to buffer. Defaults to C<3>

=item display-time

How long in seconds to display each message for. Defaults to C<20> seconds

=back

=cut

has 'messages' => is => 'ro', isa => HashRef, default => sub { {} };

=item C<model>

An immutable required object reference to the model component that is handling
the current request

=cut

has 'model' => is => 'ro', isa => Object, required => TRUE;

=item C<title>

An immutable string which defaults to null. If set will be displayed as the
application title along with the logo in the page header

=cut

has 'title' => is => 'ro', isa => Str, default => NUL;

=item C<title_abbrev>

An immutable string which defaults to C<Nav>. Used to set the pages C<title>
attribute in the HTML head. This is used in turn is used by the browser to
create history links (the back button). Would set this from configuration to
the abbreviation for the application

=cut

has 'title_abbrev' => is => 'ro', isa => Str, default => 'Nav';

=item C<title_entry>

A lazy string. The default constructor sets it to the current pages navigation
label. This is appended to C<title_abbrev> to form the labels in the browser
history

=cut

has 'title_entry' =>
   is      => 'lazy',
   isa     => Str,
   default => sub {
      my $self  = shift;
      my @parts = split m{ / }mx, $self->context->action;
      my $label = $self->_get_nav_label($parts[0] . '/' . $parts[-1]);

      return (split m{ \| }mx, $label)[0] // NUL;
   };

# Private attributes
has '_base_url' =>
   is      => 'lazy',
   isa     => class_type('URI'),
   default => sub {
      return shift->context->request->uri_for(NUL);
   };

has '_container' =>
   is      => 'lazy',
   isa     => Str,
   default => sub {
      my $self = shift;
      my $tag  = $self->container_tag;

      return $self->_html->$tag($self->_data);
   };

has '_data' =>
   is      => 'lazy',
   isa     => HashRef,
   default => sub {
      my $self     = shift;
      my $location = 'navigation-'  . $self->menu_location;
      my $display  = 'link-display-' . $self->link_display;

      return {
         'id'    => 'navigation',
         'class' => "navigation ${location} ${display}",
         'data-navigation-config' => $self->_json->encode({
            'menus'      => $self->_menus,
            'messages'   => $self->_messages,
            'moniker'    => $self->model->moniker,
            'properties' => {
               'base-url'         => $self->_base_url,
               'confirm'          => $self->confirm_message,
               'container-layout' => $self->container_layout,
               'container-name'   => $self->container_name,
               'content-name'     => $self->content_name,
               'control-icon'     => $self->control_icon,
               'icons'            => $self->icons,
               'link-display'     => $self->link_display,
               'location'         => $self->menu_location,
               'logo'             => $self->logo,
               'media-break'      => $self->media_break,
               'skin'             => $self->context->session->skin,
               'title'            => $self->title,
               'title-abbrev'     => $self->title_abbrev,
               'verify-token'     => $self->context->verification_token,
               'version'          => MCat->VERSION,
            },
         }),
      };
   };

has '_html' =>
   is      => 'ro',
   isa     => class_type('HTML::Tiny'),

lib/Web/Components/Navigation.pm  view on Meta::CPAN


If C<is_script_request> is true then stash an OK HTTP return code. When using
JS navigation all HTTP responses must be OK or the browser (which sniffs the
fetch responses) will automatically navigate

=cut

sub finalise_script_request {
   my $self = shift;

   $self->context->stash(code => HTTP_OK) if $self->is_script_request;

   return;
}

=item C<is_script_request>

Returns true if the request has come from the JS in the browser

=cut

sub is_script_request {
   my $self   = shift;
   my $header = $self->context->request->header('x-requested-with') // NUL;

   return lc $header eq 'xmlhttprequest' ? TRUE : FALSE;
}

=item C<item>

   $self = $self->item('action path', $args, $params);
   $self = $self->item(formpost, 'action path', $args, $params);

The first example will add a single link to the current C<list>. The display
text is set by the C<Nav> subroutine attribute of the endpoint, the C<href>
is supplied by C<context> C<uri_for_action>

In the second example C<formpost> is imported from L<Web::Components::Util>
and causes the rendered menu item to be a form with a button on it (it is
expected that this will be styled like a link). This is used for delete
operations since we don't do deletes with a GET

=cut

sub item {
   my ($self, @args) = @_;

   my $label;

   if (is_hashref $args[0]) {
      $label = shift @args;
      $label->{name} = $self->_get_nav_label($args[0]);
   }
   else { $label = $self->_get_nav_label($args[0]) }

   if ($self->model->is_authorised($self->context, $args[0])) {
      my $list = $self->_lists->{$self->_name}->[1];
      my ($text, $icon);

      if (is_hashref $label) {
         ($text, $icon) = split m{ \| }mx, $label->{name};
         $label->{name} = $text;
         $text = $label;
      }
      else { ($text, $icon) = split m{ \| }mx, $label }

      $icon = $self->context->request->uri_for($icon)
         if $icon && $icon =~ m{ / }mx;

      push @{$list}, [$text => $self->_uri(@args), $icon];
   }
   else { clear_redirect $self->context }

   return $self;
}

=item C<list>

   $self = $self->list('list name', 'optional title');

Sets the current list to the name provided. If this list does not exist it
is created. Once a list has been created C<item> is called to add entries to it

=cut

sub list {
   my ($self, $name, $title) = @_;

   $self->_set__name($name);

   unless (exists $self->_lists->{$name}) {
      $self->_lists->{$name} = [ $title // NUL, [] ];
      push @{$self->_order}, $name;
   }

   return $self;
}

=item C<menu>

   $self = $self->menu('list name');

If the named list exists add it to the current list. This is how you created
nested lists

=cut

sub menu {
   my ($self, $name) = @_;

   my $lists = $self->_lists;

   push @{$lists->{$self->_name}->[1]}, $name if exists $lists->{$name};

   return $self;
}

=item C<render>

Returns the HTML for inclusion on the web page

=cut

sub render {
   my $self = shift;
   my $output;

   $self->_add_global;

   try   { $output = $self->_container }
   catch { $output = $_ };

   return $output;
}

# Private methods
sub _add_global {
   my $self = shift;
   my $list = $self->list('_global');

   for my $action (@{$self->global}) {
      my ($moniker, $method) = split m{ / }mx, $action;

      if ($self->model->is_authorised($self->context, $action)) {
         if ($method and $method eq 'menu') {
            $self->context->models->{$moniker}->menu($self->context);
            $self->_set__name('_global');
         }

         push @{$self->_lists->{$self->_name}->[1]}, $moniker
            if exists $self->_lists->{$moniker};

         $list->item($action);
      }
      else { clear_redirect $self->context }
   }

   return;
}

sub _get_nav_label {
   my ($self, $action) = @_;

   my $attr = try { $self->context->get_attributes($action) };

   return $attr->{Nav}->[0] if $attr && defined $attr->{Nav};

   return NUL;
}

sub _uri {
   my ($self, @args) = @_;

   my $action = $args[0];

   return NUL if $action =~ m{ /menu \z }mx;

   return $self->context->uri_for_action(@args);
}

use namespace::autoclean;

1;

__END__

=back

=head1 Diagnostics

None

=head1 Dependencies

=over 3

=item L<Moo>

=back

=head1 Incompatibilities



( run in 0.490 second using v1.01-cache-2.11-cpan-5511b514fd6 )