view Lib/IMPL/Web/View/Metadata/FormMeta.pm @ 387:4cc6cc370fb2

DOM refactoring
author sergey
date Tue, 11 Feb 2014 01:13:47 +0400
parents 0d63f5273307
children 5aff94ba842f
line wrap: on
line source

package IMPL::Web::View::Metadata::FormMeta;
use strict;

use IMPL::lang;
use IMPL::Const qw(:prop);
use IMPL::declare {
	require => {
		Exception => 'IMPL::Exception',
		ArgException => '-IMPL::InvalidArgumentException',
		OpException => '-IMPL::InvalidOperationException',
		SchemaNavigator => 'IMPL::DOM::Navigator::SchemaNavigator',
		DOMNode => '-IMPL::DOM::Node'
	},
	base => [
		'IMPL::Web::View::Metadata::BaseMeta' => '@_'
	],
	props => [
		nodes => PROP_RO,
		decl  => PROP_RO,
		schema => PROP_RO,
		errors => PROP_RO,
		group => PROP_RO
	]
};

use constant {
	Meta => __PACKAGE__
};

sub CTOR {
	my ($this,$model,$type,$args) = @_;
	
	if ($args) {
		$this->$_($args->{$_}) foreach grep $args->{$_}, qw(decl schema nodes errors group);
	}
	
	$this->$_() || die ArgException->new($_ => "The $_ is required")
		foreach qw(schema);
}

sub GetSchemaProperty {
	my ($this,$name) = @_;
	
	return $this->decl ? $this->decl->nodeProperty($name) || $this->schema->nodeProperty($name) : $this->schema->nodeProperty($name);
}

sub template {
	shift->GetSchemaProperty('template');
}

sub label {
	shift->GetSchemaProperty('label');
}

sub inputType {
	shift->GetSchemaProperty('inputType');
}

sub inputValue {
	my ($this) = @_;
	
	if($this->isMultiple) {
		return [
			map {
				$_ ? $_->nodeValue || $_->nodeProperty('rawValue') : undef
			}
			@{$this->model || []}
		]
	} else {
		return $this->model ? $this->model->nodeValue || $this->model->nodeProperty('rawValue') : undef;
	}
}

sub isMultiple {
	my ($this) = @_;
	$this->decl && $this->decl->isMultiple;
}

sub isOptional {
	my ($this) = @_;
	not($this->decl) || $this->decl->isOptional;
}

sub GetOwnErrors {
	my ($this) = @_;
	
	my $nodes = $this->nodes;
	
	my $errors = [
		grep _IsOwnError($nodes,$this->decl,$_), @{$this->errors || []}
	];
	
	return $errors;
}

sub _IsOwnError {
    my ($nodes,$source,$err) = @_;

    #TODO: review filter
 	return 1 if ($err->node && grep($err->node == $_, @$nodes)) || (not(@$nodes) && $err->schema == $source );
    
    return 0;
}

sub _IsErrorRelates {
    my ($nodes,$source,$err) = @_;
    
    # this is an own error
    return 1 if _IsOwnError($nodes,$source,$err);
    
    # this error relates to the child control 
    
    return 0 unless @$nodes;
    
    for (my $n = $err->parent; $n ; $n = $n->parentNode) {
        return 1 if grep($n == $_, @$nodes);
    }
    
    return 0;
} 

sub PopulateProperties {
	my ($this) = @_;
	
	my @props;
		
	# return empty list of properties in case of multiple values
	return \@props if $this->isMultiple;
		
	my $navi = SchemaNavigator->new($this->schema);
	
	foreach my $decl (@{$this->schema->content->childNodes}) {
		
		my $schema = $navi->NavigateName($decl->name);
		$navi->SchemaBack();
		
		my @nodes = $this->model && $this->model->selectNodes( sub { $_->schemaNode == $decl } );
		
		my %args = (
			name => $decl->name,
			decl => $decl,
			schema => $schema,
			nodes => [@nodes],
			errors => [grep _IsErrorRelates(\@nodes,$decl,$_), @{$this->errors || []}]
		);
		
		my ($model,$type);
		
		if ($decl->isMultiple) {
			$model = [@nodes]; 
			$type = 'ARRAY';
			$args{holdingType} = $schema->type;
		} else {
			$model = shift @nodes;
			$type = $schema->type;
		}

		push @props, Meta->new($model,$type,\%args);
	}
	
	return \@props;
}

sub GetItems {
	my ($this) = @_;
	
	die OpException->new("The operation must be performed on the container")
		unless $this->isMultiple;
		
	my $i = 0;
	
	return [
		map $this->_GetItemMeta($_,$i++), @{$this->nodes}
	];
}

sub GetItem {
	my ($this,$index) = @_;
	
	die OpException->new("The operation must be performed on the container")
		unless $this->isMultiple;
		
	my $node = $this->nodes->[$index];
	
	return $this->_GetItemMeta($node,$index);
}

sub _GetItemMeta {
	my ($this,$node,$index) = @_;
	
	my @nodes;
	push @nodes,$node if $node;
	
	return Meta->new(
		$node,
		$this->schema->type,
		{
			name => $index,
			schema => $this->schema,
			errors => [grep _IsErrorRelates([$node],$this->decl,$_), @{$this->errors ||[]} ],
			group => $this,
			nodes => \@nodes
		}
	);
}

sub GetMetadataForModel {
	my ($self,$model,$args) = @_;
	
	$args ||= {};
	
	my $modelType = delete $args->{modelType};
	
	if($model) {
		die ArgException->new(model => "A node is required")
			unless is($model,DOMNode);
		
		$args->{decl} ||= $model->schemaNode;
		$args->{schema} ||= $model->schemaType; 
	}
	
	return $self->new(
		$model,
		$modelType,
		$args
	);
}

1;

__END__

=pod

=head1 NAME

=head1 SYNOPSIS

=head1 DESCRIPTION

Расширенные метаданные модели для элементов формы, помимо стандартных свойств
сожержит в себе информацию о схеме.

=head1 MEMBERS

=head2 C<[get]errors>

Ссылка на массив с ошибками при проверке схемы. Ошибки относятся ко всем
узлам в текущей модели, включая вложенные и т.п.

=head2 C<[get]model>

Ссылка на элемент документа, либо на массив с элементами для множественных
значений (C<isMultiple = true>). В том случае, когда документ был не
корректен и для не множественного элемента было передено несколько значений,
данное свойство будет содержать только первое.

=head2 C<[get]nodes>

Ссылка на массив с узлами документа. В теории количество узлов может быть
произвольным, поскольку документ может быть некорректным, т.е. их может
быть более одного в то время, как C<isMultiple = false> или, напротив, ни
одного при C<isOptional = false>.

Как правило для построения формы данное свойство не требуется.

=head2 C<[get]modelType>

Название типа данных из схемы документа (C<< schema->name >>), если тип не имеет название, то это
C<ComplexNode> для сложных узлов и C<SimpleNode> для простых.

Для моделей с множественными значениями это свойство не задано. Тип элементов
храниться в свойстве C<holdingType>

=head2 C<[get]decl>

Объявление элемента формы, объявление может совпадать со схемой в случае,
когда это был C<SimpleNode> или C<ComplexNode>, иначе это C<Node> ссылающийся
на заранее обпределенный тип.

=head2 C<[get]schema>

Схема текущего элемента, C<СomlexType>, C<SimpleType>, C<ComplexNode> или
C<SimpleNode>.

=head2 C<[get]isOptional>

Данный элемент может не иметь ни одного значения

=head2 C<[get]isMultiple>

Данный элемент может иметь более одного значения. Модель с множественными
значениями является сложным элементом, в котором дочерними моделями являются
не свойства а сами элементы, в данном случае они их именами будут индексы.

=begin code

for(my $i=0; $i< 10; $i++) {
	display_for($i,'template');
}

sub display_for {
	my ($index,$tmpl) = @_;
	
	if ($index =~ /^\d+$/) {
		return render($tmpl, metadata => { $meta->GetItem($index) });
	} else {
		return render($tmpl, metadata => { $meta->GetProperty($index) });
	}
}

=end code

=head2 C<GetOwnErrors()>

Возвращает ошибки относящиеся к самому элементу C<model>, это принципиально
для контейнеров и в случаях, когда модель не корректна и в ней присутствуют
лишние значения.  

=cut