first cut of Reaction::UI::Skin and SiteLayout VP+widget
matthewt [Wed, 30 Jan 2008 16:33:10 +0000 (16:33 +0000)]
lib/ComponentUI/Controller/Root.pm
lib/Reaction/UI/LayoutSet.pm
lib/Reaction/UI/LayoutSet/TT.pm
lib/Reaction/UI/RenderingContext/TT.pm
lib/Reaction/UI/Skin.pm [new file with mode: 0644]
lib/Reaction/UI/View.pm
lib/Reaction/UI/ViewPort/SiteLayout.pm [new file with mode: 0644]
lib/Reaction/UI/Widget/SiteLayout.pm [new file with mode: 0644]
share/skin/default/layout/site_layout.tt [moved from share/skin/default/layout/layout.tt with 98% similarity]

index a4d42ee..2e2a181 100644 (file)
@@ -6,6 +6,7 @@ use base 'Reaction::UI::Controller::Root';
 use Reaction::Class;
 
 use aliased 'Reaction::UI::ViewPort';
+use aliased 'Reaction::UI::ViewPort::SiteLayout';
 
 #
 # Sets the actions in this controller to be registered with no prefix
@@ -19,7 +20,10 @@ __PACKAGE__->config(
 
 sub base :Chained('/') :PathPart('') :CaptureArgs(0) {
   my ($self, $c) = @_;
-  $self->push_viewport(ViewPort, layout => 'layout');
+  $self->push_viewport(SiteLayout,
+    title => 'ComponentUI test title',
+    static_base_uri => "${\$c->uri_for('static')}",
+  );
 }
 
 sub root :Chained('base') :PathPart('') :Args(0) {
index de00c7c..9de03eb 100644 (file)
@@ -9,9 +9,7 @@ class LayoutSet which {
 
   has 'name' => (is => 'ro', required => 1);
 
-  has 'source_file' => (is => 'rw', lazy_fail => 1);
-
-  has 'file_extension'=> (isa => 'Str', is => 'rw', lazy_build => 1);
+  has 'source_file' => (is => 'ro', required => 1);
 
   has 'widget_class' => (
     is => 'rw', lazy_fail => 1, predicate => 'has_widget_class'
@@ -21,25 +19,11 @@ class LayoutSet which {
 
   has 'super' => (is => 'rw', predicate => 'has_super');
 
-  implements _build_file_extension => as { 'html' };
-
   implements 'BUILD' => as {
     my ($self, $args) = @_;
     my @path = @{$args->{search_path}||[]};
-    confess "No search_path provided" unless @path;
     confess "No view object provided" unless $args->{view};
-    my $found;
-    my $ext = $self->file_extension;
-    SEARCH: foreach my $path (@path) {
-      my $cand = $path->file($self->name . ".${ext}");
-      #print STDERR $cand,"\n";
-      if ($cand->stat) {
-        $self->_load_file($cand, $args);
-        $found = 1;
-        last SEARCH;
-      }
-    }
-    confess "Unable to load file for LayoutSet ".$self->name unless $found;
+    $self->_load_file($self->source_file, $args);
     unless ($self->has_widget_class) {
       $self->widget_class($args->{view}->widget_class_for($self));
     }
@@ -98,7 +82,6 @@ class LayoutSet which {
         confess "Unparseable directive ${data}";
       }
     }
-    $self->source_file($file);
   };
 
   implements '_build_widget_type' => as {
index c131bdb..7f9e2df 100644 (file)
@@ -8,7 +8,7 @@ class TT is LayoutSet, which {
 
   has 'tt_view' => (is => 'rw', isa => View, lazy_fail => 1);
 
-  implements _build_file_extension => as { 'tt' };
+  implements file_extension => as { 'tt' };
 
   implements 'BUILD' => as {
     my ($self, $args) = @_;
index 8308c0c..b359747 100644 (file)
@@ -6,11 +6,6 @@ use aliased 'Template::View';
 
 class TT is RenderingContext, which {
 
-  has 'iter_class' => (
-    is => 'ro', required => 1,
-    default => sub { 'Reaction::UI::Renderer::TT::Iter'; },
-  );
-
   our $body;
 
   implements 'dispatch' => as {
@@ -67,45 +62,5 @@ class TT is RenderingContext, which {
   };
 
 };
-  
-package Reaction::UI::Renderer::TT::Iter;
-
-use overload (
-  q{""} => 'stringify',
-  fallback => 1
-);
-
-sub pos { shift->{pos} }
-
-sub new {
-  my ($class, $cr, $rctx) = @_;
-  bless({ rctx => $rctx, cr => $cr, pos => 0 }, $class);
-}
-
-sub next {
-  my $self = shift;
-  $self->{pos}++;
-  my $next = $self->{cr}->();
-  return unless $next;
-  return sub { $next->($self->{rctx}) };
-}
-
-sub all {
-  my $self = shift;
-  my @all;
-  while (my $e = $self->next) {
-    push(@all, $e);
-  }
-  \@all;
-}
-
-sub stringify {
-  my $self = shift;
-  my $res = '';
-  foreach my $e (@{$self->all}) {
-    $res .= $e->();
-  }
-  $res;
-}
 
 1;
diff --git a/lib/Reaction/UI/Skin.pm b/lib/Reaction/UI/Skin.pm
new file mode 100644 (file)
index 0000000..ff31a3a
--- /dev/null
@@ -0,0 +1,91 @@
+package Reaction::UI::Skin;
+
+use Reaction::Class;
+
+# declaring dependencies
+use Reaction::UI::LayoutSet;
+use Reaction::UI::RenderingContext;
+
+use aliased 'Path::Class::Dir';
+
+class Skin which {
+
+  has '_layout_set_cache'   => (is => 'ro', default => sub { {} });
+
+  has 'skin_base_path' => (is => 'ro', isa => Dir, required => 1);
+
+  has 'view' => (
+    is => 'ro', required => 1, weak_ref => 1,
+    handles => [ qw(layout_set_class) ],
+  );
+
+  has 'super' => (
+    is => 'rw', isa => Skin, required => 0, predicate => 'has_super',
+  );
+
+  sub BUILD {
+    my ($self) = @_;
+    $self->_load_skin_config;
+  }
+
+  implements '_load_skin_config' => as {
+    my ($self) = @_;
+    my $base = $self->skin_base_path;
+    confess "No such skin base directory ${base}"
+      unless -d $base;
+  }
+
+  implements 'create_layout_set' => as {
+    my ($self, $name) = @_;
+    if (my $path = $self->layout_path_for($name)) {
+      return $self->layout_set_class->new(
+               $self->layout_set_args_for($name),
+               source_file => $path,
+             );
+    }
+    if ($self->has_super) {
+      return $self->super->create_layout_set($name);
+    }
+    confess "Couldn't find layout set file for ${name}";
+  };
+
+  implements 'layout_set_args_for' => as {
+    my ($self, $name) = @_;
+    return (
+      name => $name,
+      view => $self->view,
+      skin => $self,
+      ($self->has_super ? (next_skin => $self->super) : ()),
+      $self->view->layout_set_args_for($name),
+    );
+  };
+
+  implements 'layout_path_for' => as {
+    my ($self, $layout) = @_;
+    my $file_name = join(
+      '.', $layout, $self->layout_set_class->file_extension
+    );
+    my $path = $self->our_path_for_type('layout')
+                    ->file($file_name);
+    return (-e $path ? $path : undef);
+  };
+
+  implements 'search_path_for_type' => as {
+    my ($self, $type) = @_;
+    return [
+      $self->our_path_for_type($type),
+      ($self->has_super
+        ? @{$self->super->search_path_for_type($type)}
+        : ()
+      )
+    ];
+  };
+
+  implements 'our_path_for_type' => as {
+    my ($self, $type) = @_;
+    return $self->skin_base_path->subdir($type)
+  };
+
+};
+
+1;
index 5fb9056..7a1ebc5 100644 (file)
@@ -5,17 +5,25 @@ use Reaction::Class;
 # declaring dependencies
 use Reaction::UI::LayoutSet;
 use Reaction::UI::RenderingContext;
+use aliased 'Reaction::UI::Skin';
+use aliased 'Path::Class::Dir';
 
 class View which {
 
-  has '_layout_set_cache'   => (is => 'ro', default => sub { {} });
   has '_widget_class_cache' => (is => 'ro', default => sub { {} });
   has '_widget_cache' => (is => 'ro', default => sub { {} });
 
+  has '_layout_set_cache' => (is => 'ro', default => sub { {} });
+
   has 'app' => (is => 'ro', required => 1);
 
   has 'skin_name' => (is => 'ro', required => 1);
 
+  has 'skin' => (
+    is => 'ro', lazy_build => 1,
+    handles => [ qw(create_layout_set search_path_for_type) ]
+  );
+
   has 'layout_set_class' => (is => 'ro', lazy_build => 1);
 
   has 'rendering_context_class' => (is => 'ro', lazy_build => 1);
@@ -30,20 +38,20 @@ class View which {
     return $self->find_related_class('RenderingContext');
   };
 
+  implements '_build_skin' => as {
+    my ($self) = @_;
+    Skin->new(
+      name => $self->skin_name, view => $self,
+      skin_base_path => # returns a File, not a Dir. Thanks, Catalyst.
+        Dir->new($self->app->path_to('share', 'skin', $self->skin_name)),
+    );
+  };
+
   implements 'COMPONENT' => as {
     my ($class, $app, $args) = @_;
     return $class->new(%{$args||{}}, app => $app);
   };
 
-  sub BUILD{
-    my $self = shift;
-    my $skin_name = $self->skin_name;
-    #XXX i guess we will add the path to installed reaction templates here
-    my $skin_path = $self->app->path_to('share','skin',$skin_name);
-    confess("'${skin_path}' is not a valid path for skin '${skin_name}'")
-      unless -d $skin_path;
-  }
-
   implements 'render_window' => as {
     my ($self, $window) = @_;
     my $root_vp = $window->focus_stack->vp_head;
@@ -72,25 +80,33 @@ class View which {
   implements 'widget_class_for' => as {
     my ($self, $layout_set) = @_;
     my $base = $self->blessed;
-    my $tail = $layout_set->widget_type;
-    my $lset_name = $layout_set->name;
-    # eventually more stuff will go here i guess?
+    my $widget_type = $layout_set->widget_type;
     my $app_name = ref $self->app || $self->app;
-    my $cache = $self->_widget_class_cache;
-    return $cache->{ $lset_name } if exists $cache->{ $lset_name };
-
-    my @search_path = ($base, $app_name, 'Reaction::UI');
-    my @haystack    = map { join '::', $_, 'Widget', $tail } @search_path;
-    for my $class (@haystack){
-      #here we should throw if exits and error instead of eating the error
-      #only next when !exists
-      eval { Class::MOP::load_class($class) };
-      #$@ ? next : return  $class;
-      #warn "Loaded ${class}" unless $@;
-      $@ ? next : return $cache->{ $lset_name } = $class;
-    }
-    confess "Couldn't load widget '$tail' for layout '$lset_name': tried: " .
-      join(", ", @haystack);
+    return $self->_widget_class_cache->{$widget_type} ||= do {
+
+      my @search_path = ($base, $app_name, 'Reaction::UI');
+      my @haystack    = map { join('::', $_, 'Widget', $widget_type) }
+                          @search_path;
+      my $found;
+      foreach my $class (@haystack) {
+        #here we should throw if exits and error instead of eating the error
+        #only next when !exists
+        eval { Class::MOP::load_class($class) };
+        #$@ ? next : return  $class;
+        #warn "Loaded ${class}" unless $@;
+        #warn "Boom loading ${class}: $@" if $@;
+        unless ($@) {
+          $found = $class;
+          last;
+        }
+      }
+      unless ($found) {
+        confess "Couldn't load widget '${widget_type}'"
+                ." for layout '${\$layout_set->name}':"
+                ." tried: ".join(", ", @haystack);
+      }
+      $found;
+    };
   };
 
   implements 'layout_set_for' => as {
@@ -110,13 +126,6 @@ class View which {
     return $cache->{$lset_name} ||= $self->create_layout_set($lset_name);
   };
 
-  implements 'create_layout_set' => as {
-    my ($self, $name) = @_;
-    return $self->layout_set_class->new(
-             $self->layout_set_args_for($name),
-           );
-  };
-
   implements 'find_related_class' => as {
     my ($self, $rel) = @_;
     my $own_class = ref($self) || $self;
@@ -133,25 +142,6 @@ class View which {
     confess "Unable to find related ${rel} class for ${own_class}";
   };
 
-  implements 'layout_set_args_for' => as {
-    my ($self, $name) = @_;
-    return (
-      name => $name,
-      search_path => $self->layout_search_path,
-      view => $self,
-    );
-  };
-
-  implements 'layout_search_path' => as {
-    my ($self) = @_;
-    return $self->search_path_for_type('layout');
-  };
-
-  implements 'search_path_for_type' => as {
-    my ($self, $type) = @_;
-    return [ $self->app->path_to('share','skin',$self->skin_name,$type) ];
-  };
-
   implements 'create_rendering_context' => as {
     my ($self, @args) = @_;
     return $self->rendering_context_class->new(
@@ -164,6 +154,10 @@ class View which {
     return ();
   };
 
+  implements 'layout_set_args_for' => as {
+    return ();
+  };
+
 };
 
 1;
diff --git a/lib/Reaction/UI/ViewPort/SiteLayout.pm b/lib/Reaction/UI/ViewPort/SiteLayout.pm
new file mode 100644 (file)
index 0000000..4560dc6
--- /dev/null
@@ -0,0 +1,14 @@
+package Reaction::UI::ViewPort::SiteLayout;
+
+use Reaction::Class;
+use aliased 'Reaction::UI::ViewPort';
+
+class SiteLayout is ViewPort, which {
+
+  has 'title' => (isa => 'Str', is => 'rw', lazy_fail => 1);
+
+  has 'static_base_uri' => (isa => 'Str', is => 'rw', lazy_fail => 1);
+
+};
+
+1;
diff --git a/lib/Reaction/UI/Widget/SiteLayout.pm b/lib/Reaction/UI/Widget/SiteLayout.pm
new file mode 100644 (file)
index 0000000..73406a9
--- /dev/null
@@ -0,0 +1,15 @@
+package Reaction::UI::Widget::SiteLayout;
+
+use Reaction::UI::WidgetClass;
+use aliased 'Reaction::UI::Widget::Container';
+
+class SiteLayout is Container, which {
+
+  after fragment widget {
+    arg static_base => $_{viewport}->static_base_uri;
+    arg title => $_{viewport}->title;
+  };
+
+};
+
+1;
similarity index 98%
rename from share/skin/default/layout/layout.tt
rename to share/skin/default/layout/site_layout.tt
index 15b535b..0a1448a 100644 (file)
@@ -1,5 +1,3 @@
-=widget Container
-
 =for layout widget
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">