changeset 367:608e74bc309f

form metadata, mostly done
author cin
date Tue, 03 Dec 2013 17:55:36 +0400
parents 935629bf80df
children 010ceafd0c5a
files Lib/IMPL/Web/View/Metadata/BaseMeta.pm Lib/IMPL/Web/View/Metadata/FormMeta.pm Lib/IMPL/Web/View/Metadata/FormProvider.pm _test/Resources/person.schema.xml _test/Resources/person_info.xml _test/Test/Web/View.pm _test/Test/Web/ViewSelector.pm
diffstat 7 files changed, 227 insertions(+), 264 deletions(-) [+]
line wrap: on
line diff
--- a/Lib/IMPL/Web/View/Metadata/BaseMeta.pm	Mon Dec 02 17:44:38 2013 +0400
+++ b/Lib/IMPL/Web/View/Metadata/BaseMeta.pm	Tue Dec 03 17:55:36 2013 +0400
@@ -6,7 +6,8 @@
 use IMPL::declare {
 	require => {
 		Exception => 'IMPL::Exception',
-		ArgException => '-IMPL::InvalidArgumentException'
+		ArgException => '-IMPL::InvalidArgumentException',
+		NotImplException => '-IMPL::NotImplementedException'
 	},
 	base => [
 		'IMPL::Object' => undef
@@ -14,8 +15,6 @@
 	props => [
 		model => PROP_RO,
 		modelType => PROP_RO,
-		holdingType => PROP_RO,
-		provider => PROP_RO,
 		name => PROP_RO,
 		label => PROP_RO,
 		container => PROP_RO,
@@ -27,27 +26,22 @@
 };
 
 sub CTOR {
-	my ($this,$provider,$model,$type,$args) = @_;
-	
-	$type ||= typeof($model);
+	my ($this,$model,$type,$args) = @_;
 	
-	die ArgException->new(provider => 'A provider must be specified');
-	
-	$this->provider($provider);
 	$this->model($model);
 	$this->modelType($type);
-	$this->childMap({});
+	$this->_childMap({});
 	
 	#mixin other args
 	if ($args) {
-		$this->$_($args->{$_}) foreach grep $args->{$_}, qw(name label container template holdingType);
+		$this->$_($args->{$_}) foreach grep $args->{$_}, qw(name label container template);
 	}
 }
 
 sub GetProperty {
 	my ($this,$name) = @_;
 	
-	$this->_PopulateProperties()
+	$this->GetProperties()
 		unless $this->_childNames;
 	
 	return $this->_childMap->{$name};
@@ -59,38 +53,38 @@
 	if ($this->_childNames) {
 		return [ map $this->_childMap->{$_}, @{$this->_childNames} ];
 	} else {
-		return $this->_PopulateProperties;
+		my @childNames;
+		my %childMap;
+		my @result; 
+	
+		foreach my $child (@{$this->PopulateProperties($this)}) {
+			$childMap{$child->name} = $child;
+			push @childNames, $child->name;
+			push @result, $child;
+		}
+		
+		$this->_childMap(\%childMap);
+		$this->_childNames(\@childNames);
+		return \@result;
 	}	
 }
 
-sub _PopulateProperties {
+sub PopulateProperties {
 	my ($this) = @_;
 	
-	my @childNames;
-	my %childMap;
-	my @result; 
-
-	foreach my $child (@{$this->provider->PopulateProperties($this)}) {
-		$childMap{$child->name} = $child;
-		push @childNames, $child->name;
-		push @result, $child;
-	}
-	
-	$this->_childMap(\%childMap);
-	$this->_childNames(\@childNames);
-	return \@result;
+	die NotImplException->new();
 }
 
 sub GetItems {
 	my ($this) = @_;
 	
-	return $this->provider->GetItems($this);
+	die NotImplException->new();
 }
 
 sub GetItem {
 	my ($this,$index) = @_;
 	
-	return $this->provider->GetItem($this,$index);
+	die NotImplException->new();
 }
 
 1;
--- a/Lib/IMPL/Web/View/Metadata/FormMeta.pm	Mon Dec 02 17:44:38 2013 +0400
+++ b/Lib/IMPL/Web/View/Metadata/FormMeta.pm	Tue Dec 03 17:55:36 2013 +0400
@@ -5,7 +5,9 @@
 use IMPL::declare {
 	require => {
 		Exception => 'IMPL::Exception',
-		ArgException => '-IMPL::InvalidArgumentException'
+		ArgException => '-IMPL::InvalidArgumentException',
+		OpException => '-IMPL::InvalidOperationException',
+		SchemaNavigator => 'IMPL::DOM::Navigator::SchemaNavigator'
 	},
 	base => [
 		'IMPL::Web::View::Metadata::BaseMeta' => '@_'
@@ -14,43 +16,152 @@
 		nodes => PROP_RO,
 		decl  => PROP_RO,
 		schema => PROP_RO,
-		errors => PROP_RO
+		errors => PROP_RO,
+		group => PROP_RO
 	]
 };
 
+use constant {
+	Meta => __PACKAGE__
+};
+
 sub CTOR {
-	my ($this,$provider,$model,$type,$args) = @_;
+	my ($this,$model,$type,$args) = @_;
 	
 	if ($args) {
-		$this->$_($args->{$_}) foreach grep $args->{$_}, qw(decl schema nodes errors);
+		$this->$_($args->{$_}) foreach grep $args->{$_}, qw(decl schema nodes errors group);
 	}
 	
 	$this->$_() || die ArgException->new($_ => "The $_ is required")
-		foreach qw(decl schema);
+		foreach qw(schema);
 }
 
 sub isMultiple {
-	shift->decl->isMultiple;
+	my ($this) = @_;
+	$this->decl && $this->decl->isMultiple;
 }
 
 sub isOptional {
-	shift->decl->isOptional;
+	my ($this) = @_;
+	not($this->decl) || $this->decl->isOptional;
 }
 
 sub GetOwnErrors {
 	my ($this) = @_;
 	
-	my $node = undef;
-	
-	$node = not($this->isMultiple) && $this->nodes ? $this->nodes->[0] : undef;
+	my $nodes = $this->nodes;
 	
 	return [
-		grep {
-			($node && $_->node && $_->node == $node) || (not($node) && $_->schema == $this->decl )
-		} @{$this->errors || []}
+		grep _IsOwnError($nodes,$this->decl,$_), @{$this->errors || []}
 	];
 }
 
+sub _IsOwnError {
+    my ($nodes,$source,$err) = @_;
+    
+    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 { $_->schemaSource == $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} = $decl->type;
+		} else {
+			$model = shift @nodes;
+			$type = $decl->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->model || []}
+	];
+}
+
+sub GetItem {
+	my ($this,$index) = @_;
+	
+	die OpException->new("The operation must be performed on the container")
+		unless $this->isMultiple;
+		
+	my $node = $this->model->[$index];
+	
+	return $this->GetItemMeta($node,$index);
+}
+
+sub _GetItemMeta {
+	my ($this,$node,$index) = @_;
+	
+	return Meta->new(
+		$node,
+		$this->decl->type,
+		{
+			name => $index,
+			schema => $this->schema,
+			errors => [grep _IsOwnError([$node],$this->decl,$_), @{$this->errors ||[]} ],
+			group => $this
+		}
+	);
+}
+
 1;
 
 __END__
--- a/Lib/IMPL/Web/View/Metadata/FormProvider.pm	Mon Dec 02 17:44:38 2013 +0400
+++ b/Lib/IMPL/Web/View/Metadata/FormProvider.pm	Tue Dec 03 17:55:36 2013 +0400
@@ -30,6 +30,7 @@
 	foreach my $decl (@{$meta->schema->content->childNodes}) {
 		
 		my $schema = $navi->NavigateName($decl->name);
+		$navi->SchemaBack();
 		
 		my @nodes = $meta->model && $meta->model->selectNodes( sub { $_->schemaSource == $decl } );
 		
@@ -52,34 +53,21 @@
 			$type = $decl->type;
 		}
 
-		push @props, Meta->new(
-			$this,
-			\@nodes,
-			$decl->type,
-			{
-				name => $decl->name,
-				schema => $schema,
-			}
-		)
+		push @props, Meta->new($this,$model,$type,\%args);
 	}
 }
 
+sub GetItems {
+	my ($this,$meta) = @_;
+	
+	if ($meta->isMultiple)
+}
+
+sub GetItem {
+	my ($this,$meta,$index) = @_;
+}
+
 
-sub _IsErrorRelates {
-    my ($nodes,$source,$err) = @_;
-    
-    # this is an own error
-    return 1 if ($err->node && grep($err->node == $_, @$nodes)) || (not(@$nodes) && $err->schema == $source );
-    
-    # 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;
-} 
+
 
 1;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/_test/Resources/person.schema.xml	Tue Dec 03 17:55:36 2013 +0400
@@ -0,0 +1,20 @@
+<?xml version='1.0' encoding='utf-8'?>
+<schema>
+	<ComplexNode name='personInfo'>
+		<NodeSet>
+			<SimpleNode name='firstName'/>
+			<SimpleNode name='lastName'/>
+			<Node name='age' type='number' minOccur='0'/>
+			<Node name='address' type='address' minOccur='0' maxOccur='unbounded'/>
+		</NodeSet>
+	</ComplexNode>
+	<ComplexType type='address'>
+		<NodeSet>
+			<SimpleNode name='street'/>
+			<Node name='line' type='number' minOccur='0'/>
+		</NodeSet>
+	</ComplexType>
+	<SimpleType type='number'>
+		<RegExp>\d+</RegExp>
+	</SimpleType>
+</schema>
\ No newline at end of file
--- a/_test/Resources/person_info.xml	Mon Dec 02 17:44:38 2013 +0400
+++ b/_test/Resources/person_info.xml	Tue Dec 03 17:55:36 2013 +0400
@@ -6,5 +6,9 @@
 		<street>hellroad</street>
 		<line>1</line>
 	</address>
+	<address>
+		<street>waypass</street>
+		<line>2</line>
+	</address>
 </personInfo>
 
--- a/_test/Test/Web/View.pm	Mon Dec 02 17:44:38 2013 +0400
+++ b/_test/Test/Web/View.pm	Tue Dec 03 17:55:36 2013 +0400
@@ -4,21 +4,26 @@
 use warnings;
 use utf8;
 
-use parent qw(IMPL::Test::Unit);
-__PACKAGE__->PassThroughArgs;
-
 use File::Slurp;
 use Scalar::Util qw(weaken);
 use Data::Dumper;
 use IMPL::lang;
 use IMPL::Test qw(assert assertarray test GetCallerSourceLine);
-use IMPL::Web::View::TTLoader();
 
-use constant {
-    TTLoader => 'IMPL::Web::View::TTLoader',
-    MProfiler => 'IMPL::Profiler::Memory'
+use IMPL::declare {
+	require => {
+		FormMeta => 'IMPL::Web::View::Metadata::FormMeta',
+		Schema => 'IMPL::DOM::Schema',
+		Builder => 'IMPL::DOM::Navigator::Builder',
+		Document => 'IMPL::DOM::Document',
+		XMLReader => 'IMPL::DOM::XMLReader'
+	},
+	base => [
+		'IMPL::Test::Unit' => '@_'
+	]
 };
 
+
 sub AssertMemoryLeak {
     my $code = shift;
     my $dump = shift;
@@ -33,199 +38,40 @@
 }
 
 sub templatesDir {
-    $_[0]->GetResourceDir('Resources','TTView');
-}
-
-sub CreateLoader {
-    my ($this) = @_;
-    
-    my $loader = TTLoader->new(
-        {
-            INCLUDE_PATH => [
-                $this->templatesDir
-            ],
-            INTERPOLATE => 1,
-            POST_CHOMP => 1,
-            ENCODING => 'utf-8'
-        },
-        ext => '.tt',
-        initializer => 'global.tt',
-        globals => {
-            site => {
-                name => 'Test Site'
-            },
-            date => {
-                now => sub { localtime(time); }
-            },
-            dynamic => sub { 'this is a dynamic value' },
-            view => {
-            }
-        },
-        layoutBase => 'Layout'
-    );
+    shift->GetResourceDir('Resources','TTView');
 }
 
-test TTLoaderTests => sub {
-    my ($this) = @_;
-    
-    my $loader = $this->CreateLoader();
-    
-    # test the loader to be able to find a desired resource
-    assert( defined($loader->template('simple') ) );
-    
-    # loader should be initialized on demand
-    assert( not $loader->isInitialized );
-    
-    # loader should be able to load a document
-    my $doc = $loader->document('simple');
-    assert(defined $doc);
-    
-    assert( $loader->isInitialized );
-    assert( $loader->context->stash->get('user') eq 'test_user');
-    
-    # document should inherit loader's context 
-    assert( $doc->context->stash->get('user') eq 'test_user');
-    
-    # document should not have 'this' template variable
-    
-    assert( $doc->context != $loader->context); # document should have an own context
-};
+test TestFormMetadata => sub {
+	my ($this) = @_;
+	
+	my $schema = Schema->LoadSchema($this->GetResourceFile('Resources','person.schema.xml'));
+	my $builder = Builder->new(Document, $schema);
+	
+	my $reader = XMLReader->new( Navigator => $builder );
+	
+	$reader->ParseFile("Resources/person_info.xml");
+	
+	my $doc = $builder->document;
+	my @errors = $builder->buildErrors;
+	
+	push @errors, $schema->Validate($doc);
+	
+	my $meta = FormMeta->new(
+		$doc,
+		$doc->schemaSource->type,
+		{
+			decl => $doc->schemaSource,
+			schema => $doc->schema,
+			errors => \@errors
+		}
+	); 
 
-test TTDocumentTests => sub {
-    my ($this) = @_;
-    my $loader = $this->CreateLoader();
-    
-    my $doc = $loader->document('simple');
-    
-    assert(defined $doc);
-    
-    $doc->title('test document');
-    $doc->name('document');
-    
-    assert($doc->name eq 'document');
-    assert($doc->title eq 'test document');
-    
-    assert(not $doc->can('notexists')); # autoloaded property should be ignored
-    assert(not defined $doc->notexists); # nonexisting property 
-    assert($doc->template->version == 10); # static metadata
-    assert($doc->context->stash->get('user') eq 'test_user' ); # runtime context should be derived from documentContext
-    
-    my $text = $doc->Render();
-    my $expected = read_file($this->GetResourceFile('Resources','TTView.Output','simple.txt'), binmode => ':utf8');
-    
-    assert($text eq $expected, "Bad Render() output","Got: $text", "Expected: $expected");
-    
+	my $props = $meta->GetProperties;
+	
+	my $prop = $meta->GetProperty('address');
+	
+	join ',', map $_->GetProperty('line')->name, @{$prop->GetItems};
 };
 
-test TestDocumentLayout => sub {
-    my ($this) = @_;
-    
-    my $loader = $this->CreateLoader();
-    
-    my $doc = $loader->document(
-        'complex',
-        {
-            data => [qw(one two three)],
-            site => {
-                name => 'Test Site'
-            }
-        }
-    );
-    
-    assert($doc->layout eq 'default');
-    
-    my $text = $doc->Render();
-    my $expected = read_file($this->GetResourceFile('Resources', 'TTView.Output', 'complex.default.txt'), binmode => ':utf8' );
-    my ($text_raw,$expected_raw) = ($text, $expected);
-    $text_raw =~ s/\s+//g;
-    $expected_raw =~ s/\s+//g;
-    assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got:      $text", "Expected: $expected"); 
-};
-
-test TestControlInheritance => sub {
-    my ($this) = @_;
-    
-    my $loader = $this->CreateLoader();
-    my $doc = $loader->document('derived');
-    
-    my $text = $doc->Render();
-    my $expected = read_file($this->GetResourceFile('Resources', 'TTView.Output', 'derived.txt'), binmode => ':utf8' );
-    
-    my ($text_raw,$expected_raw) = ($text, $expected);
-    $text_raw =~ s/\s+//g;
-    $expected_raw =~ s/\s+//g;
-    
-    assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got:      $text", "Expected: $expected");
-};
-
-test TestDocumentsIsolation => sub {
-	my $this = shift;
-	
-	my $loader = $this->CreateLoader();
-    
-    my $doc = $loader->document('simple');
-    
-    assert(ref $loader->context->stash->get([ 'dojo', 0, 'require', 0]) eq 'ARRAY');
-    assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]);
-    assert($loader->context->stash != $doc->context->stash);
-    
-    assert(defined $doc);
-    
-    # only root stash variables can be localized, to avoid modifying dojo we
-    # need to replace it completely 
-    $doc->context->process(\q{
-    	[% SET dojo = { require => [] } %]
-    	[% dojo.require.push('dijit/form/TextBox') %]
-    	[% SET user = 'dummy guy' %]
-    });
-    
-    assert($doc->context->stash->get('user') eq 'dummy guy');
-    assert($loader->context->stash->get('user') eq 'test_user');
-    assertarray($doc->context->stash->get([ 'dojo', 0, 'require', 0]),['dijit/form/TextBox']);
-    assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]);
-    
-    my $text = $doc->Render();
-    
-    my $doc2 = $loader->document('complex');
-    
-    assertarray($doc2->context->stash->get([ 'dojo', 0, 'require', 0]),[]);
-    
-    # This document has a layout ehich will replace 'dojo' global variable.
-    # The layout contains INIT block which runs first in the context of the
-    # document, then RenderContent is called and then the layout is applied
-    $doc2->Render();
-    
-    assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]);
-    
-    # TODO: to be able to rendered multiple times, Render shouldn't affect the context of the document
-    #assertarray($doc2->context->stash->get([ 'dojo', 0, 'require', 0]),[]);
-};
-
-test TestMemoryLeaks => sub {
-    my ($this) = @_;
-    
-    my $loader = $this->CreateLoader();
-    $loader->document('simple'); # force loader initialization
-    
-    AssertMemoryLeak(sub {        
-        my $doc = $loader->document('simple');
-    },'dump');
-        
-    AssertMemoryLeak(sub {
-        my $doc = $loader->document('simple');
-        $doc->Render( { self => $doc } );
-    },'dump');
-    
-    $loader->template('Layout/default');
-    $loader->template('My/Org/Panel');
-    $loader->template('My/Org/TextPreview');
-    
-    $loader->template('complex');
-    AssertMemoryLeak(sub {
-        my $doc = $loader->document('complex');
-        $doc->Render();
-    },'dump');
-    
-};
 
 1;
\ No newline at end of file
--- a/_test/Test/Web/ViewSelector.pm	Mon Dec 02 17:44:38 2013 +0400
+++ b/_test/Test/Web/ViewSelector.pm	Tue Dec 03 17:55:36 2013 +0400
@@ -5,7 +5,7 @@
 use IMPL::Test qw(test assert assertarray);
 use IMPL::declare {
     require => {
-        TTView => 'IMPL::Web::Handler::TTView'        
+        TTView => 'IMPL::Web::Handler::View'        
     },
     base => [
         'IMPL::Test::Unit' => '@_'