view Lib/IMPL/Web/View/TTContext.pm @ 348:f116cd9fe7d9

working on TTView: pre-alpha version
author cin
date Thu, 03 Oct 2013 19:48:57 +0400
parents 3eafa6fefa9f
children 86b470004d47
line wrap: on
line source

package IMPL::Web::View::TTContext;
use strict;
use Template::Base;
use Carp qw(carp);

use IMPL::Exception();
use IMPL::lang qw(is typeof hashApply);
use IMPL::declare {
	require => {
	   Document => '-Template::Document',
	   TypeKeyedCollection => 'IMPL::TypeKeyedCollection',
	   ArgException => "-IMPL::InvalidArgumentException"
	},
	base => [
		'Template::Context' => '@_'
	]
};

BEGIN {
	no strict 'refs';
	foreach my $prop (qw(
	   root
	   base
	   tt_ext
	   tt_cache
	   parent
	   prefix
	   cache
	   includes
	)) {
		my $t = $prop;
		
		*{__PACKAGE__ . '::' . $prop} = sub {
			my $this = shift;
			return @_ ? $this->stash->set($t, @_) : $this->stash->get($t);
		}
	}
}

sub clone {
	my $this = shift;
	my $params = shift;
	
	$this->localise();	

	my $args = { %{$this} };

	$this->delocalise();
	
	my $class = ref($this);
	
    delete $args->{CONFIG};
    
    my $clone = $class->new($args);
    
    $clone->stash->update($params) if $params;
    
    return $clone;
}

sub find_template {
	my ($this,$name) = @_;
	
	my $cache = $this->tt_cache;
	$this->tt_cache($cache = {}) unless $cache;
	
	if(my $tpl = $cache->{$name}) {
        return $tpl;
	}
	
	my @inc = ($this->base, @{$this->includes || []});
	
	my $ext = $this->tt_ext || "";
	
	my $file;
    
	foreach my $dir (@inc) {
		$file = $dir ? "$dir/$name" : $name;
		
		my $base = join('/',splice([split(/\/+/,$file)],0,-1));
		
		$file =  $ext ? "$file.$ext" : $file;
		
		warn "lookup: $file";
		
		my $tt = eval { $this->template($file) };
		
		return $cache->{$name} = {
			base => $base,
			template => $tt,
		} if $tt;
	}
	
	$this->throw(Template::Constants::ERROR_FILE, "$name: not found");
}

sub display {
	my $this = shift;
	my $model = shift;
	my ($template, $args);
	
	if (ref $_[0] eq 'HASH') {
		$args = shift;
	} else {
		$template = shift;
		$args = shift;
	}
	
	my $prefix = $this->prefix;
	
	if (not(($args and delete $args->{'-no-resolve'}) or ref $model)) {
		$prefix = $prefix ? "${prefix}.${model}" : $model;
		$model = $this->resolve_model($model);
	} else {
		$prefix = "";
	}
	
	$template = $template ? $this->find_template($template) : $this->find_template_for($model);
	
	return $this->render(
        $template,
        hashApply(
            {
                prefix => $prefix,
                model => $model,
	        },
            $args
        )
    );
}

sub invoke_environment {
	my ($this,$code,$env) = @_;
	
	$env ||= {};
	
	my $out = eval {
		$this->localise(
            hashApply(
	            {
					root => $this->root || $this,
					cache => TypeKeyedCollection->new(),
		            display => sub {
		                $this->display(@_);
		            },
		            render => sub {
		            	$this->render(@_);
		            }
				},
                $env
            )
        );
		
		&$code($this);
	};
	
	my $e = $@;
	$this->delocalise();
	
	die $e if $e;
    
    return $out;
}

sub render {
	my ($this,$template,$args) = @_;
	
	$args ||= {};
	
	#TODO handle classes
	
	my $base;
	
	$template = $this->find_template($template) unless ref $template;
	
	if (ref $template eq 'HASH') {
        $base = $template->{base};
        $template = $template->{template};
    } else {
        carp "got an invalid template object: $template";
        $base = $this->base;
    }
	
	return $this->invoke_environment(
	   sub {
	       return shift->include($template,$args);
	   },
	   {
	   	base => $base,
	   	parent => $this
	   }
	)
}

sub resolve_model {
	my ($this,$prefix) = @_;
	
	die ArgException->new(prefix => "the prefix must be specified")
	   unless defined $prefix;
	
	#TODO handle DOM models
	
	my @comp = map { $_, 0 } grep length($_), split(/\.|\[(\d+)\]/, $prefix);
	
	return $this->stash->get(['model',0,@comp]);
}

sub find_template_for {
	my ($this,$model) = @_;
	
	my $type = typeof($model);
	
	return $this->find_template('templates/plain') unless $type;
	
	if (my $template = $this->cache->Get($type)) {
		return $template;
	} else {
		
		no strict 'refs';
               
        my @isa = $type;
        
        while (@isa) {
            my $sclass = shift @isa;
            
            (my $name = $sclass) =~ s/:+/_/g;

            $template = $this->find_template("templates/$name");
            
            if ($template) {
            	$this->cache->Set($sclass,$template);
            	return $template;
            } 
                
            push @isa, @{"${sclass}::ISA"};
        }
		
	}
	
	return;
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Web::View::TTContext> - доработанная версия контекста

=head1 DESCRIPTION

Расширяет функции C<Template::Context>

=begin plantuml

@startuml

object RootContext {
    document
    globals
}

object DocumentContext {
    base
    extends
}

object ControlContext {
    base
    extends
}

RootContext o-- DocumentContext 
RootContext o-- ControlContext 

Document -- DocumentContext
Control - ControlContext

Loader . RootContext: <<creates>>
Loader . Document: <<creates>>
Loader -up- Registry

@enduml

=end plantuml

=head1 MEMBERS

=head2 C<[get,set]base>

Префикс пути для поиска шаблонов

=head2 C<template($name)>

Сначала пытается загрузить шаблон используя префикс C<base>, затем без префикса.

=head2 C<clone()>

Создает копию контекста, при этом C<stash> локализуется, таким образом
клонированный контекст имеет собственное пространство имен, вложенное в
пространство родительского контекста.

=cut