view Lib/IMPL/Web/TT/Form.pm @ 234:2530d1bb9638

sync
author sergey
date Thu, 11 Oct 2012 20:11:45 +0400
parents 4d0e1962161c
children
line wrap: on
line source

use strict;
package IMPL::Web::TT::Form;

use parent qw(IMPL::Web::TT::Control);

use IMPL::Class::Property;
use IMPL::DOM::Navigator::SchemaNavigator();

__PACKAGE__->PassThroughArgs;

BEGIN {
    public property base => prop_all;
    public property schema => prop_all;
    public property errors => prop_all;
    public property data => prop_all;
    public property state => prop_all;
    public property formResult => prop_all;
}

sub CTOR {
    my ($this) = @_;
    
    if (my $form = $this->formResult) {
        $this->base($form->{formName});
        $this->errors($form->{formErrors});
        $this->data($form->{formData});
        $this->schema($form->{formSchema});
        $this->state($form->{state});
    } else {
    
        $this->base($this->nodeName) unless $this->base;
        
        die new IMPL::InvalidArgumentException('A schema is required for a form',$this->nodeName)
            unless eval { $this->schema->isa( typeof IMPL::DOM::Schema ) };
        
        die new IMPL::InvalidOperationException('Can\'t find a form definition in a schema',$this->nodeName,$this->base)
            unless $this->schema->selectNodes(sub { $_->nodeName eq 'ComplexNode' and $_->name eq $this->base });
    }
            
    $this->errors([]) unless $this->errors;
}

sub fillContents {
    my ($this) = @_;
    
    my $schema = $this->schema->selectSingleNode(sub { $_->nodeName eq 'ComplexNode' and $_->name eq $this->base });
    
    $this->buildContainer(
        $schema,
        $schema,
        $this->data->isComplex ? $this->data : undef,
        $this
    );
}

sub buildContainer {
    my ($this,$schemaSource,$schema,$domNode,$container,$path) = @_;
    
    $path = [@{$path || []},{node => $domNode, schemaSource => $schemaSource}];
    
    $container ||= $this->document->Create($schemaSource->name,'IMPL::Web::TT::Collection');
    
    foreach my $schemaItem ( $schema->content->childNodes ) {
        my $schemaItemSource = $schemaItem;
        
        $schemaItem = $this->schema->resolveType($schemaItem->type)
            if typeof $schemaItem eq typeof IMPL::DOM::Schema::Node;
            
        my @nodesData = $domNode->selectNodes(sub { $_->schemaSource == $schemaItemSource } ) if $domNode;
        
        push @nodesData, undef unless @nodesData;
            
        if ($schemaItem->isa(typeof IMPL::DOM::Schema::ComplexNode) ) {
            $this->appendChild( $this->buildContainer($schemaItemSource,$schemaItem,$_,undef,$path) ) foreach @nodesData;
        } elsif ($schemaItem->isa(typeof IMPL::DOM::Schema::SimpleNode)) {
            $this->appendChild( $this->buildControl($schemaItemSource,$schemaItem,$_,$path) ) foreach @nodesData;
        }
    }
    
    return $container;
}

sub buildControl {
    my ($this,$schemaSource,$schema,$node,$path) = @_;
    
    my @errors;
    
    if ($node) {
        @errors = grep { ($_->Node || $_->Parent) == $node } @{$this->errors}; 
    } else {
        @errors = grep $_->Schema == $schemaSource, @{$this->errors};
    }
    
    return $this->document->CreateControl(
        $schemaSource->name,
        $this->mapType($schemaSource),
        {
            schema => $schema,
            sourceSchema => $schemaSource,
            errors => \@errors,
            data => $node,
            inputType => $schemaSource->nodeProperty('inputType') || $schema->nodeProperty('inputType'),
            nodeValue => $node && $node->nodeValue, # small hack set a non dom class property through
            queryParameter => $this->makeParameterName([@$path,{ node => $node, schemaSource => $schemaSource}])
        }
    );
}

sub mapType {
    my ($this,$schema) = @_;
    
    $schema->nodeProperty('control') ||
    ( $schema->type && $this->schema->resolveType($schema->type)->nodeProperty('control') )
        or die new IMPL::Exception("Unable to get control class for the form element",$schema->path);
}

sub makeParameterName {
    my ($this,$path) = @_;
    
    join '/', map {
        $_->{node} ?
            (
                $_->{node}->nodeProperty('instanceId') ?
                    $_->{node}->nodeName . '['. ']' :
                    $_->{node}->nodeName
            ) :
            (
                $_->{schemaSource}->maxOccur eq 'unbounded' || $_->{schemaSource}->maxOccur > 1 ?
                    $_->{schemaSource}->name . '[0]' :
                    $_->{schemaSource}->name
            ) 
    } @$path;
}

sub makeControlArgs{
    my ($this,$path) = @_;
    
    my $navi = new IMPL::DOM::Navigator::SchemaNavigator($this->schema);
    my @path = ($this->base, split(/\./,$path) ); 
    
    $navi->NavigateName($_) or die new IMPL::InvalidArgumentException(
        "Can't find a definition for an element",
        $_,
        $path,
        $this->element,
    ) foreach @path;
    
    my $schema = $navi->Current;
    my $sourceSchema = $navi->SourceSchemaNode;
    my $queryParameter = join '/', @path;
    shift @path;
    my $node = $this->data ? $this->data->selectSingleNode(@path) : undef;
    
    my @errors;
    
    if ($node) {
        @errors = grep { ($_->Node || $_->Parent) == $node } @{$this->errors}; 
    } else {
        @errors = grep $_->Schema == $sourceSchema, @{$this->errors};
    }
    
    return {
        schema => $schema,
        sourceSchema => $sourceSchema,
        errors => \@errors,
        data => $node,
        nodeValue => $node && $node->nodeValue, # small hack set a non dom class property through
        queryParameter => $queryParameter,
        inputType => $sourceSchema->nodeProperty('inputType') || $schema->nodeProperty('inputType')
    };
}

sub makeContent {
    my ($this,$mappings) = @_;
    
    my $formSchema = $this->schema->selectSingleNode(sub { $_->nodeName eq 'ComplexNode' and $_->name eq $this->base } )
        or die new Exception("Cant find a schema element for the specified form", $this->base);

    my $doc = $this->document;        
    foreach my $itemSchema ( $formSchema->content->childNodes ) {
        my $itemName = $itemSchema->name; 
        if (my $controlClass = $mappings->{$itemName} ) {
            my $contorl = $doc->CreateControl($itemName,$controlClass,$this->makeControlArgs($itemName));
            $this->appendChild($contorl);
        }
    }
    return;
}

sub formErrors {
    my ($this) = @_;
    
    if (my $node = $this->data ) {
        return [
            grep {
                ( $_->Node || $_->Parent) == $node
            } @{$this->errors}
        ];
    } else {
        return [];
    }
}

1;
__END__

=pod

=head1 NAME

C<IMPL::Web::TT::Form> - Форма с элементами

=head1 DESCRIPTION

Является элементом управления, тоесть для своего преобразования ипользует
шаблон 

=cut