| 
49
 | 
     1 package Form::Container;
 | 
| 
 | 
     2 use strict;
 | 
| 
 | 
     3 use Common;
 | 
| 
 | 
     4 use Form::Filter;
 | 
| 
 | 
     5 use base qw(Form::Item);
 | 
| 
 | 
     6 
 | 
| 
 | 
     7 BEGIN {
 | 
| 
 | 
     8     DeclareProperty Schema => ACCESS_READ;
 | 
| 
 | 
     9     DeclareProperty Children => ACCESS_READ;
 | 
| 
 | 
    10 }
 | 
| 
 | 
    11 
 | 
| 
 | 
    12 sub CTOR {
 | 
| 
 | 
    13     my ($this,%args) = @_;
 | 
| 
 | 
    14     $args{Schema} or die new Exception('A schema is required');
 | 
| 
 | 
    15     
 | 
| 
 | 
    16     $this->SUPER::CTOR(@args{qw(Id Form Parent Attributes)});
 | 
| 
 | 
    17     $this->{$Schema} = $args{Schema};
 | 
| 
 | 
    18 }
 | 
| 
 | 
    19 
 | 
| 
 | 
    20 sub ResolveItem {
 | 
| 
 | 
    21     my ($this,$ItemId) = @_;
 | 
| 
 | 
    22     
 | 
| 
 | 
    23     if (my $schemaChild = $this->{$Schema}->FindChild($ItemId->Name)) {
 | 
| 
 | 
    24         if ($schemaChild->isMulti) {
 | 
| 
 | 
    25             defined $ItemId->InstanceID or die new Exception('Instance id is required for a muti element');
 | 
| 
 | 
    26             if (my $child = $this->{$Children}{$ItemId->Name}[$ItemId->InstanceID]){
 | 
| 
 | 
    27                 return $child;
 | 
| 
 | 
    28             } else {
 | 
| 
 | 
    29                 return undef if not $this->Form->AutoCreate;
 | 
| 
 | 
    30                 return $this->{$Children}{$ItemId->Name}[$ItemId->InstanceID] = $this->Form->CreateInstance($schemaChild,$ItemId,$this);
 | 
| 
 | 
    31             }
 | 
| 
 | 
    32             
 | 
| 
 | 
    33         } else {
 | 
| 
 | 
    34             defined $ItemId->InstanceID and die new Exception('The child is a single element',$this->Id->Canonical,$ItemId->Name);
 | 
| 
 | 
    35             if(my $child = $this->{$Children}{$ItemId->Name}) {
 | 
| 
 | 
    36                 return $child;
 | 
| 
 | 
    37             } else {
 | 
| 
 | 
    38                 return undef if not $this->Form->AutoCreate;
 | 
| 
 | 
    39                 return $this->{$Children}{$ItemId->Name} = $this->Form->CreateInstance($schemaChild,$ItemId,$this);
 | 
| 
 | 
    40             }
 | 
| 
 | 
    41         }
 | 
| 
 | 
    42     } else {
 | 
| 
 | 
    43         die new Exception('The requested item isn\'t exists in the schema', $this->Id->Canonical,$ItemId->Name);
 | 
| 
 | 
    44     }
 | 
| 
 | 
    45 }
 | 
| 
 | 
    46 
 | 
| 
 | 
    47 sub isEmpty {
 | 
| 
 | 
    48     my ($this) = @_;
 | 
| 
 | 
    49 
 | 
| 
 | 
    50     foreach my $child (values %{$this->{$Children} || {} }) {
 | 
| 
 | 
    51         if (ref $child eq 'ARRAY') {
 | 
| 
 | 
    52             foreach my $inst (@$child) {
 | 
| 
 | 
    53                 return 0 if not $child->isEmpty;
 | 
| 
 | 
    54             }
 | 
| 
 | 
    55         } else {
 | 
| 
 | 
    56             return 0 if not $child->isEmpty;
 | 
| 
 | 
    57         }
 | 
| 
 | 
    58     }
 | 
| 
 | 
    59 
 | 
| 
 | 
    60     return 1;
 | 
| 
 | 
    61 }
 | 
| 
 | 
    62 
 | 
| 
 | 
    63 =pod
 | 
| 
 | 
    64 Получает дочерние контенеры в виде списка, при том только не пустые контейнеры.
 | 
| 
 | 
    65 Если дочернний контейнер не множественный, то список будет состоять из одного элемента.
 | 
| 
 | 
    66 =cut
 | 
| 
 | 
    67 sub GetChild {
 | 
| 
 | 
    68     my ($this,$name) = @_;
 | 
| 
 | 
    69     return unless exists $this->{$Children}{$name};
 | 
| 
 | 
    70     return( grep $_, map { UNIVERSAL::isa($_,'ARRAY') ? @{$_} : $_ } $this->{$Children}{$name} );
 | 
| 
 | 
    71 }
 | 
| 
 | 
    72 
 | 
| 
 | 
    73 =pod
 | 
| 
 | 
    74 Выполняет фильтры по схеме для себя и всех детей.
 | 
| 
 | 
    75 Фильтры определяются по схеме и вызываются в различнх контекстах
 | 
| 
 | 
    76 
 | 
| 
 | 
    77 * сначала для группы,
 | 
| 
 | 
    78 * потом для детишек, причем если
 | 
| 
 | 
    79     * детишки множественные, то
 | 
| 
 | 
    80         * снсчала для набора детишек, а потом
 | 
| 
 | 
    81         * для каждого в отдельности
 | 
| 
 | 
    82 =cut
 | 
| 
 | 
    83 sub Validate {
 | 
| 
 | 
    84     my ($this,$rhDisableFilters) = @_;
 | 
| 
 | 
    85     
 | 
| 
 | 
    86     $rhDisableFilters ||= {};
 | 
| 
 | 
    87 
 | 
| 
 | 
    88     my @errors;
 | 
| 
 | 
    89 
 | 
| 
 | 
    90     foreach my $filter (grep {$_->SUPPORTED_CONTEXT & (Form::Filter::CTX_SINGLE) and not exists $rhDisableFilters->{$_}} map {$_->Instance} $this->{$Schema}->Filters) {
 | 
| 
 | 
    91         my $result = $filter->Invoke($this,Form::Filter::CTX_SINGLE | Form::Filter::CTX_EXISTENT,$this->{$Schema});
 | 
| 
 | 
    92         if ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
 | 
| 
 | 
    93             return ();
 | 
| 
 | 
    94         } elsif ($result->State == Form::FilterResult::STATE_ERROR) {
 | 
| 
 | 
    95             push @errors,$result;
 | 
| 
 | 
    96         }
 | 
| 
 | 
    97     }
 | 
| 
 | 
    98 
 | 
| 
 | 
    99     CHILD_LOOP: foreach my $schemaChild ($this->{$Schema}->Children) {
 | 
| 
 | 
   100         
 | 
| 
 | 
   101         if ($schemaChild->isMulti) {
 | 
| 
 | 
   102             my %DisableFilters;
 | 
| 
 | 
   103             foreach my $filter (grep {$_->SUPPORTED_CONTEXT & Form::Filter::CTX_SET} map {$_->Instance} $schemaChild->Filters) {
 | 
| 
 | 
   104                 
 | 
| 
 | 
   105                 my $result = $filter->Invoke($this->{$Children}{$schemaChild->Name},Form::Filter::CTX_SET,$schemaChild,$this);
 | 
| 
 | 
   106                 if ($result->State == Form::FilterResult::STATE_ERROR) {
 | 
| 
 | 
   107                     push @errors,$result;
 | 
| 
 | 
   108                     # не проверять другие фильтры вообще
 | 
| 
 | 
   109                     next CHILD_LOOP;
 | 
| 
 | 
   110                 } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
 | 
| 
 | 
   111                     # не проверять другие фильтры вообще
 | 
| 
 | 
   112                     next CHILD_LOOP;
 | 
| 
 | 
   113                 } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STAY) {
 | 
| 
 | 
   114                     # не проверять данный фильтр на каждом экземпляре
 | 
| 
 | 
   115                     $DisableFilters{$filter} = 1;
 | 
| 
 | 
   116                 } else {
 | 
| 
 | 
   117                     # STATE_SUCCESS - все ок
 | 
| 
 | 
   118                 }
 | 
| 
 | 
   119             }
 | 
| 
 | 
   120             
 | 
| 
 | 
   121             $_ and push @errors,$_->Validate(\%DisableFilters) foreach grep !$_->isEmpty, $this->GetChild($schemaChild->Name);
 | 
| 
 | 
   122             
 | 
| 
 | 
   123         } else {
 | 
| 
 | 
   124             my %DisableFilters;
 | 
| 
 | 
   125             
 | 
| 
 | 
   126             # проверяем фильтры, которые могут применяться на несуществующем значении
 | 
| 
 | 
   127             foreach my $filter (grep { $_->SUPPORTED_CONTEXT & Form::Filter::CTX_SINGLE and not $_->SUPPORTED_CONTEXT & Form::Filter::CTX_EXISTENT} map {$_->Instance} $schemaChild->Filters) {
 | 
| 
 | 
   128                 my $result = $filter->Invoke($this->{$Children}{$schemaChild->Name},Form::Filter::CTX_SINGLE,$schemaChild,$this);
 | 
| 
 | 
   129                 
 | 
| 
 | 
   130                 if ($result->State == Form::FilterResult::STATE_ERROR) {
 | 
| 
 | 
   131                     push @errors,$result;
 | 
| 
 | 
   132                     # не проверять другие фильтры вообще
 | 
| 
 | 
   133                     next CHILD_LOOP;
 | 
| 
 | 
   134                 } elsif ($result->State == Form::FilterResult::STATE_SUCCESS_STOP) {
 | 
| 
 | 
   135                     # не проверять другие фильтры вообще
 | 
| 
 | 
   136                     next CHILD_LOOP;
 | 
| 
 | 
   137                 } else {
 | 
| 
 | 
   138                     # STATE_SUCCESS(_STAY) - все ок
 | 
| 
 | 
   139                     $DisableFilters{$filter} = 1;
 | 
| 
 | 
   140                 }
 | 
| 
 | 
   141             }
 | 
| 
 | 
   142             
 | 
| 
 | 
   143             # если значение существует, то применяем оставшиеся фильтры
 | 
| 
 | 
   144             push @errors,$this->{$Children}{$schemaChild->Name}->Validate(\%DisableFilters) if $this->{$Children}{$schemaChild->Name};
 | 
| 
 | 
   145         }
 | 
| 
 | 
   146         
 | 
| 
 | 
   147     }
 | 
| 
 | 
   148     
 | 
| 
 | 
   149     return @errors;
 | 
| 
 | 
   150 }
 | 
| 
 | 
   151 
 | 
| 
 | 
   152 sub Dispose {
 | 
| 
 | 
   153     my ($this) = @_;
 | 
| 
 | 
   154     
 | 
| 
 | 
   155     foreach my $child (values %{ $this->{$Children} || {} }) {
 | 
| 
 | 
   156         if (ref $child eq 'ARRAY') {
 | 
| 
 | 
   157             foreach my $inst (@$child) {
 | 
| 
 | 
   158                 $inst->Dispose;
 | 
| 
 | 
   159             }
 | 
| 
 | 
   160         } else {
 | 
| 
 | 
   161             die new IMPL::Exception("Child is null",%{ $this->{$Children} }) if not $child;
 | 
| 
 | 
   162             $child->Dispose;
 | 
| 
 | 
   163         }
 | 
| 
 | 
   164     }
 | 
| 
 | 
   165     
 | 
| 
 | 
   166     delete @$this{$Schema,$Children};
 | 
| 
 | 
   167     
 | 
| 
 | 
   168     $this->SUPER::Dispose;
 | 
| 
 | 
   169 }
 | 
| 
 | 
   170 1;
 |