Group
Extension

Catalyst-ControllerPerContext/lib/Catalyst/ControllerRole/View.pm

package Catalyst::ControllerRole::View;

use Moose::Role;
use Scalar::Util;
use String::CamelCase;

requires 'ctx';

our %global_content_prefixes = (
  'HTML' => ['application/xhtml+xml', 'text/html'],
  'JSON' => ['application/json'],
  'XML' => ['application/xml', 'text/xml'],
  'JS' => ['application/javascript', 'text/javascript'],
);

sub view {
  my ($self, @args) = @_;
  return $self->ctx->stash->{current_view_instance} if exists($self->ctx->stash->{current_view_instance}) && !@args;
  return $self->view_for($self->ctx->action, @args);
}

sub view_at {
  my ($self, $view_name_proto, @args) = @_;
  my ($view_name, $view_fragment) = split(/\./, $view_name_proto);
  push @args, view_fragment => $view_fragment if $view_fragment;

  $view_name = "@{[$self->path_prefix]}/$view_name" if $self->path_prefix;
  my $namepart = String::CamelCase::camelize($view_name);
  $namepart =~s/\//::/g;

  my $view = $self->_build_view_name($namepart);
  $self->ctx->log->debug("Initializing View: $view") if $self->ctx->debug;
  return $self->ctx->view($view, @args);
}

sub view_for {
  my ($self, $proto, @args) = @_;
  my ($action_proto, $view_fragment) = Scalar::Util::blessed($proto)
    ? ($proto, undef)
    : split(/\./, $proto);
  
  push @args, view_fragment => $view_fragment if $view_fragment;

  my $action = Scalar::Util::blessed($action_proto)
    ? $action_proto
    : $self->action_for($action_proto);

  return $self->view_at($proto, @args) unless $action; # Not sure if this is right

  my $action_namepart = $self->_action_namepart_from_action($action);
  my $view = $self->_build_view_name($action_namepart);

  $self->ctx->log->debug("Initializing View: $view") if $self->ctx->debug;
  return $self->ctx->view($view, @args);
}

sub _action_namepart_from_action {
  my ($self, $action) = @_;
  my $action_namepart = String::CamelCase::camelize($action->reverse);
  $action_namepart =~s/\//::/g;
  return $action_namepart;
}

sub _build_view_name {
  my ($self, $action_namepart) = @_;

  my $accept = $self->ctx->request->headers->header('Accept');
  my $available_content_types = $self->_content_negotiation->{content_types};
  my $content_type = $self->_content_negotiation->{negotiator}->choose_media_type($available_content_types, $accept);
  my $matched_content_type = $self->_content_negotiation->{content_types_to_prefixes}->{$content_type};

  $self->ctx->log->warn("no matching type for $accept") unless $matched_content_type;
  $self->ctx->detach_error(406, +{error=>"Requested not acceptable."}) unless $matched_content_type;
  $self->ctx->log->debug( "Content-Type: $content_type, Matched: $matched_content_type") if $self->ctx->debug;

  my $view = $self->_view_from_parts($matched_content_type, $action_namepart);
  return $view if $view;

  $self->ctx->log->warn("Using default prefix 'HTML'"); 
  return $self->_view_from_parts('HTML', $action_namepart); 
}

sub _view_from_parts {
  my ($self, $matched_content_type, $action_namepart) = @_;
  my $view = join('::', $matched_content_type, $action_namepart);
  $self->ctx->log->debug("Negotiated View: $view") if $self->ctx->debug;

  my $appclass = ref($self->ctx);
  my $check = "${appclass}::View::${view}";
  my $comps = $self->ctx->components;
  return $view if exists $comps->{$check};

  $self->ctx->log->warn("View not found: $check");
  return undef;
}

has '_content_negotiation' => (is => 'ro', required=>1);

sub process_component_args {
  my ($class, $app, $args) = @_;

  my $n = HTTP::Headers::ActionPack->new->get_content_negotiator;
  my %content_prefixes = (
    %global_content_prefixes,
    %{ delete($args->{content_prefixes}) || +{} },
  );
  my @content_types = map { @$_ } values %content_prefixes;
  my %content_types_to_prefixes = map {
    my $prefix = $_; 
    map {
      $_ => $prefix
    } @{$content_prefixes{$prefix}}
  } keys %content_prefixes;

  return +{
    %$args,
    _content_negotiation => +{
      content_prefixes => \%content_prefixes,
      content_types_to_prefixes => \%content_types_to_prefixes,
      content_types => \@content_types,
      negotiator => $n,
    },
  };
}

1;

=head1 NAME
 
Catalyst::ControllerRole::View - Call Views from your Controller

=head1 SYNOPSIS

    package Example::Controller::Register;

    use Moose;
    use MooseX::MethodAttributes;

    extends 'Catalyst::ControllerPerRequest';
    with 'Catalyst::ControllerRole::View';


=head1 DESCRIPTION

Experimental role for L<Catalyst::ControllerPerRequest> that calls a view based on the
the namespace.  This is a work in progress and may change in the future.

I'm not documenting this more, if you can't follow the source you shouldn't be
using this.   I may trash it eventually.

=head1 ALSO SEE
 
L<Catalyst::Runtime>, L<Catalyst::Controller>

=head1 AUTHOR

    John Napiorkowski <jjnapiork@cpan.org>

=head1 COPYRIGHT
 
    2023

=head1 LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
 


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.