[Catalyst-commits] r8434 - in trunk/Catalyst-Example-InstantCRUD: lib lib/Catalyst/Example lib/Catalyst/Example/Controller lib/Catalyst/Helper lib/Catalyst/Helper/Controller lib/Catalyst/Helper/View script t

zby at dev.catalyst.perl.org zby at dev.catalyst.perl.org
Thu Sep 18 20:37:27 BST 2008


Author: zby
Date: 2008-09-18 20:37:27 +0100 (Thu, 18 Sep 2008)
New Revision: 8434

Removed:
   trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Example/InstantCRUD/
   trunk/Catalyst-Example-InstantCRUD/lib/DBIx/
   trunk/Catalyst-Example-InstantCRUD/lib/HTML/
Modified:
   trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Example/Controller/InstantCRUD.pm
   trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/Controller/InstantCRUD.pm
   trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/InstantCRUD.pm
   trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/View/InstantCRUD.pm
   trunk/Catalyst-Example-InstantCRUD/script/instantcrud.pl
   trunk/Catalyst-Example-InstantCRUD/t/00.createapp.t
   trunk/Catalyst-Example-InstantCRUD/t/10.apptest.t
   trunk/Catalyst-Example-InstantCRUD/t/21.test_dvdzbr.t
Log:
Based on Rose::HTML::Form

Modified: trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Example/Controller/InstantCRUD.pm
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Example/Controller/InstantCRUD.pm	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Example/Controller/InstantCRUD.pm	2008-09-18 19:37:27 UTC (rev 8434)
@@ -2,13 +2,13 @@
 
 use warnings;
 use strict;
-use base 'Catalyst::Base';
+use base 'Catalyst::Controller';
 
 use Carp;
 use Data::Dumper;
 use HTML::Widget;
 use Path::Class;
-use HTML::Widget::DBIC;
+use Rose::HTMLx::Form::DBIC qw/ dbic_from_form options_from_resultset init_with_dbic /;
 
 use version; our $VERSION = qv('0.0.12');
 
@@ -32,17 +32,6 @@
     return $1;
 }
 
-sub model_widget {
-    my ( $self, $c, $id ) = @_;
-    my $fieldsconfig = $self->load_interface_config($c);
-    my $model_name = $c->config->{InstantCRUD}{model_name};
-    my $rs = $self->model_resultset($c);
-    my $item = $rs->find($id);
-    my $w = HTML::Widget::DBIC->create_from_config( $fieldsconfig, $rs, $item ); 
-    $w->method( 'post' );
-    return $w;
-}
-
 sub model_item {
     my ( $self, $c, $id ) = @_;
     my $rs = $self->model_resultset($c);
@@ -59,6 +48,7 @@
 
 sub index : Private {
     my ( $self, $c ) = @_;
+    $c->stash->{template} = lc( $self->source_name ) . '/list.tt';
     $c->forward('list');
 }
 
@@ -74,68 +64,41 @@
           ->action( $c->uri_for( 'destroy', $id ) );
         $w->element( 'Submit', 'ok' )->value('Delete ?');
         $c->stash->{destroywidget} = $w->process;
-        $c->stash->{template}      = 'destroy.tt';
+#        $c->stash->{template}      = 'destroy.tt';
     }
 }
 
-sub do_add : Local {
-    my ( $self, $c ) = @_;
-    my $w = $self->model_widget( $c );
-    my $fs = $w->element( 'Fieldset', 'Submit' );
-    $fs->element( 'Submit', 'ok' )->value('Create');
-    my $result = $w->action( $c->uri_for('do_add') )->process( $c->request );
-    if ( $result->has_errors ) {
-        $c->stash->{widget}   = $result;
-        $c->stash->{template} = 'edit.tt';
+sub edit : Local {
+    my ( $self, $c, $id ) = @_; 
+    my $form_name = ref( $self ) . '::' . $self->source_name . 'Form';
+    my $form = $form_name->new();
+    warn $form->html_table;
+    my $rs = $self->model_resultset($c);
+    options_from_resultset( $form, $rs );
+    my $params = $c->req->params;
+    $params->{id} = $id;
+    $form->params( $params );
+#    $form->prepare();
+    $form->init_fields();
+    if( $c->req->method eq 'POST' and $form->was_submitted ){
+        if ( $form->validate ){
+            my $item = dbic_from_form($form, $rs);
+            $c->res->redirect( $c->uri_for( 'view', $item->id ) );
+        }
     }
     else {
-        my $item = $result->save_to_db();
-        $c->forward( 'view', [ $item->id ] );
+        init_with_dbic($form, $rs->find( $id )) if $id;
     }
+    $c->stash( form => $form );
 }
 
-sub add : Local {
-    my ( $self, $c ) = @_;
-    my $w = $self->model_widget($c);
-    my $fs = $w->element( 'Fieldset', 'Submit' );
-    $fs->element( 'Submit', 'ok' )->value('Create');
-    $c->stash->{widget} = $w->action( $c->uri_for('do_add') )->process;
-    $c->stash->{template} = 'edit.tt';
-}
 
-sub do_edit : Local {
-    my ( $self, $c, $id ) = @_;
-    die "You need to pass an id" unless $id;
-    my $w    = $self->model_widget( $c, $id );
-    my $fs = $w->element( 'Fieldset', 'Submit' );
-    $fs->element( 'Submit', 'ok' )->value('Update');
-    my $result = $w->action( $c->uri_for('do_edit') )->process( $c->request );
-    if ( $result->has_errors ) {
-        $c->stash->{widget}   = $result;
-        $c->stash->{template} = 'edit.tt';
-    }
-    else {
-        $result->save_to_db();
-        $c->forward('view', [ $id ]);
-    }
-}
-
-sub edit : Local {
-    my ( $self, $c, $id ) = @_;
-    die "You need to pass an id" unless $id;
-    my $w = $self->model_widget( $c, $id );
-    my $fs = $w->element( 'Fieldset', 'Submit' );
-    $fs->element( 'Submit', 'ok' )->value('Update');
-    $c->stash->{widget} = $w->action( $c->uri_for('do_edit', $id) )->process();
-    $c->stash->{template} = 'edit.tt';
-}
-
 sub view : Local {
     my ( $self, $c, $id ) = @_;
     die "You need to pass an id" unless $id;
     my $item = $self->model_item( $c, $id );
     $c->stash->{item} = $item;
-    $c->stash->{template} = 'view.tt';
+#    $c->stash->{template} = 'view.tt';
 }
 
 sub get_resultset {
@@ -183,7 +146,8 @@
     ($c->stash->{pri}) = $source->primary_columns;
     $c->stash->{order_by_column_link} = $self->create_col_link($c, $source);
     $c->stash->{result} = $result;
-    $c->stash->{template} = 'list.tt';
+#    $c->stash->{template} = 'list.tt';
+    warn $c->uri_for();
 }
 
 
@@ -237,9 +201,6 @@
 =item model_item
 Returns an item from the model.
 
-=item model_widget
-Returns a L<HTML::Widget> object filled with elements from the model.
-
 =item source_name
 Class method for finding name of corresponding database table.
 

Modified: trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/Controller/InstantCRUD.pm
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/Controller/InstantCRUD.pm	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/Controller/InstantCRUD.pm	2008-09-18 19:37:27 UTC (rev 8434)
@@ -6,127 +6,46 @@
 use strict;
 use Path::Class;
 use Data::Dumper;
+use Rose::HTMLx::Form::DBIC::FormGenerator;
 
 sub mk_compclass {
-    my ( $self, $helper, $schema, $attrs) = @_;
+    my ( $self, $helper, $schema, $m2m) = @_;
     
+    my $dir = dir( $helper->{dir}, 'root' );
+    $helper->mk_dir($dir);
     # controllers
     my @source_monikers = $schema->sources;
     for my $class( @source_monikers ) {
-        $helper->{class} = $helper->{app} . '::Controller::' . $class;
-        (my $file = $helper->{file})  =~ s/InstantCRUD/$class/;
-        $helper->{columns} = [ _getcolumns( $schema->source($class) ) ];
-        $helper->{belongsto} = [ _getbelongsto( $schema->source($class) ) ];
-        $helper->{relations} = [ _getrelations( $schema->source($class), $attrs ) ];
-        $helper->render_file( compclass => $file );
-#        $helper->render_file( altcompclass => $file );
-#        $helper->render_file( altcompclass => $file . '.alt' );
+        mk_controller( $helper, $class, $schema, $m2m );
     }
-    my $interface_config_file = file( $helper->{dir}, 'interface_config.dat' );
-    my $interface_config = Dumper( $attrs->{config} );
-    $helper->mk_file( $interface_config_file, $interface_config);
 }
 
-sub _getrelations {
-    my ($table, $attrs) = @_;
-    my @columns;
-    my $inflector = DBIx::Class::Schema::Loader::RelBuilder->new;
-    my %mmrels;
-    for my $mmrel ( keys %{$attrs->{many_to_many_relation_table}} ){
-        $mmrels{$inflector->_inflect_plural($mmrel)} = $attrs->{many_to_many_relation_table}{$mmrel};
-    }
-    my $relatedclass;
-    for my $col ($table->relationships){
-        next if $table->has_column($col);
-        if( defined $mmrels{$col} ){
-            my @m2m = @{$mmrels{$col}};
-            if ($m2m[0] eq $table->name){
-                $col = $m2m[1];
-            }else{
-                $col = $m2m[2];
-            }
-            $relatedclass = ucfirst $col;  #horrible
-            $col = $inflector->_inflect_plural($col); #horror
-        }else{
-            my $info = $table->relationship_info( $col );
-            $relatedclass = $info->{class};
-        }
-
-        my $label = join ' ', map { ucfirst } split '_', $col;
-        push @columns, {widgettype => 'DoubleSelect', name => $col, label => $label, relatedclass => $relatedclass };
-    }
-    return @columns;
+sub mk_controller {
+    my( $helper, $class, $schema, $m2m ) = @_;
+    $helper->{class} = $helper->{app} . '::Controller::' . $class;
+    (my $file = $helper->{file})  =~ s/InstantCRUD/$class/;
+    my $generator = Rose::HTMLx::Form::DBIC::FormGenerator->new( 
+        schema => $schema, 
+        class_prefix => $helper->{class}, 
+        style => 'single', 
+        m2m => $m2m,
+    );
+    $helper->{form_code} = $generator->generate_form( $class );
+    warn "form code for $class: " . $helper->{form_code};
+    $helper->render_file( compclass => $file );
 }
 
-sub _getbelongsto {
-    my $table = shift;
-    my @columns;
-    for my $col ($table->columns){
-        next if !$table->relationship_info($col);
-        my $info = $table->column_info($col);
-        my $label = $info->{label} || join ' ', map { ucfirst } split '_', $col;
-        push @columns, {widgettype => 'Select', name => $col, label => $label };
-    }
-    return @columns;
-}
 
-sub _getcolumns {
-    my $table = shift;
-    my @columns;
-    my %primary_columns = map {$_ => 1} $table->primary_columns;
-    for my $col ($table->columns){
-        my $info = $table->column_info($col);
-        next if $info->{is_auto_increment};
-        next if $primary_columns{$col};
-        next if $table->relationship_info($col);
-        my $size = $info->{size} || 40;
-        my $label = $info->{label} || join ' ', map { ucfirst } split '_', $col;
-        my ( $widgettype, @constraints);
-        if ( $info->{data_type} =~ /int/i ) {
-            push @constraints,  {
-                constraint => 'Integer',
-                message => 'Should be a number',
-            }
-        } elsif ( $info->{size} ) {
-            push @constraints,  {
-                constraint => 'Length',
-                message => "Should be shorten than $info->{size} characters",
-                method => 'max',
-                arg    => $info->{size},
-            };
-        }
-        if ( !$info->{is_nullable} && !$info->{is_auto_increment}){
-            push @constraints,  {
-                constraint => 'All',
-                message => "The field is required",
-            }
-        }
-        if ( $col =~ /password|passwd/ ) {
-            $size = 40 if $size > 40;
-            $widgettype = 'Password';
-           push @constraints,  {
-                constraint => 'Equal',
-                args => "$col\_2",
-                message => "Passwords must match",
-            }, {
-                constraint => 'AllOrNone',
-                args => "$col\_2", 
-                message => "Confirm the password",
-            };
-            push @columns, {widgettype => $widgettype, name => $col, label => $label, size => $size, constraints => \@constraints};
-            push @columns, {widgettype => $widgettype, name => $col .'_2', label => $label, size => $size, constraints => \@constraints};
-         }else{
-            if( $size > 80 ){
-                $widgettype = 'Textarea';
-            }else{
-                $widgettype = 'Textfield';
-            } 
-            push @columns, {widgettype => $widgettype, name => $col, label => $label, size => $size, constraints => \@constraints};
-        }
-    }
-    return @columns;
+
+sub _strip_class {
+    my $fullclass = shift;
+    my @parts     = split /::/, $fullclass;
+    my $class     = pop @parts;
+    return $class;
 }
 
+
+
 1; # Magic true value required at end of module
 __DATA__
 
@@ -134,72 +53,13 @@
 
 __compclass__
 package [% class %];
-use base Catalyst::Example::Controller::InstantCRUD;
+use base "Catalyst::Example::Controller::InstantCRUD";
 use strict;
 
-1;
-__altcompclass__
-package [% class %];
-use base Catalyst::Example::Controller::InstantCRUD;
-use strict;
+[% form_code %]
 
-sub model_widget {
-    my ( $self, $c, $id ) = @_;
-    my $item = $self->model_item( $c, $id ) if $id;
-    my $table = $self->model_resultset( $c )->result_source();
-    my $w = HTML::Widget->new();
-    my ($element, $info, @options, $relatedclass, $ref_pk, $const);
-    [% FOR col = columns %]
-      $element = $w->element( '[% col.widgettype %]', '[% col.name %]' );
-      $element->label( '[% col.label%]' );
-      $element->value( $item->[% col.name %] ) if $id;
-      [% IF col.widgettype == 'Textfield' %]
-        $element->size([% col.size %]);
-        $element->maxlength([% col.size %]);
-      [% END %]
-      [% FOR const = col.constraints %]
-        $const = $w->constraint( '[% const.constraint %]', '[% col.name %]' [% IF const.args %] , '[% const.args %]' [% END %] )->message( '[% const.message %]' );
-        [% IF const.method %]
-        $const->[% const.method %]([% const.arg %]);
-        [% END %]
-      [% END %]
-    [% END %]
-    my $model = $c->model($c->config->{InstantCRUD}{model_name});
-    [% FOR col = belongsto %]
-      $element = $w->element( '[% col.widgettype %]', '[% col.name %]' );
-      $element->label( '[% col.label%]' );
-      @options = ( 0, '' );
-      $info = $table->relationship_info('[% col.name %]');
-      $relatedclass = $model->resultset($info->{source});
-      $ref_pk = ( $relatedclass->result_source->primary_columns ) [0];
-      for my $ref_item ( $relatedclass->search() ){
-          push @options, $ref_item->$ref_pk, "$ref_item"; 
-      }
-      $element->options(@options);
-      $element->selected( $item->[% col.name %] ) if $id;
-    [% END %]
-    [% FOR col = relations %]
-      $element = $w->element( '[% col.widgettype %]', '[% col.name %]' );
-      $element->label( '[% col.label%]' );
-      $element->multiple(1)->size(5);
-      @options = ( 0, '' );
-      $relatedclass = $model->resultset('[% col.relatedclass %]');
-      $ref_pk = ( $relatedclass->result_source->primary_columns ) [0];
-      for my $ref_item ( $relatedclass->search() ){
-          push @options, $ref_item->$ref_pk, "$ref_item"; 
-      }
-      $element->options(@options);
-      if ( $id ) {
-          my @selected = $item->[% col.name %]();
-          $element->selected(
-              [ @selected ? map( $_->$ref_pk, @selected ) : 0 ] );
-      } 
-      $element->selected( $item->[% col.name %] ) if $id;
-    [% END %]
-    return HTML::Widget->new('widget')->method('post')->embed($w);
-}
-
 1;
+
 __END__
 
 =head1 NAME
@@ -234,6 +94,8 @@
 
 =item mk_compclass
 
+=item mk_controller
+
 =back
 
 =head1 INTERFACE 

Modified: trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/InstantCRUD.pm
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/InstantCRUD.pm	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/InstantCRUD.pm	2008-09-18 19:37:27 UTC (rev 8434)
@@ -49,15 +49,15 @@
 	StackTrace
 	HTML::Widget
 [% IF auth -%]
-	Authentication
-	Authentication::Store::DBIC
-	Authentication::Credential::Password
-	Auth::Utils
 [% END -%]
 	Session
 	Session::Store::FastMmap
 	Session::State::Cookie
 /;
+#	Authentication
+#	Authentication::Store::DBIC
+#	Authentication::Credential::Password
+#	Auth::Utils
 
 our $VERSION = '0.01';
 
@@ -156,9 +156,9 @@
 Action available only for logged in users.  Checks if user is logged in, if not, forwards to login page.
 =cut
 
-sub restricted : Local : ActionClass('Auth::Check') {
-    my ( $self, $c ) = @_;
-}
+# sub restricted : Local : ActionClass('Auth::Check') {
+#     my ( $self, $c ) = @_;
+# }
 
 
 =head2 login
@@ -167,7 +167,7 @@
 
 =cut
 
-sub login : Local : ActionClass('Auth::Login') {}
+# sub login : Local : ActionClass('Auth::Login') {}
 
 =head2 logout
 
@@ -175,7 +175,7 @@
 
 =cut
 
-sub logout : Local : ActionClass('Auth::Logout') {}
+# sub logout : Local : ActionClass('Auth::Logout') {}
 [% END %]
 
 =head2 end
@@ -209,24 +209,6 @@
     model_name: [% model_name %]
     schema_name: [% schema_name %]
     maxrows: 10
-[% IF auth %]
-authentication:
-    dbic:
-        failled_logon_message: 'Bad username or password.'
-        user_class: [% model_name %]::[% auth.user_class %]
-        user_field: [% auth.user_field %]
-        password_field: [% auth.password_field %]
-        password_type: [% auth.password_type %]
-        password_hash_type: [% auth.password_hash_type %]
-[% END; IF authz %]
-authorization:
-    dbic:
-        role_class: [% model_name %]::[% authz.role_class %]
-        role_source: [% authz.role_class %]
-        role_field: [% authz.role_field %]
-        role_rel: [% authz.role_rel %]
-        user_role_user_field: [% authz.user_role_user_field %]
-[% END %]
 __END__
 
 =head1 NAME

Modified: trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/View/InstantCRUD.pm
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/View/InstantCRUD.pm	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/lib/Catalyst/Helper/View/InstantCRUD.pm	2008-09-18 19:37:27 UTC (rev 8434)
@@ -6,14 +6,13 @@
 use strict;
 use Carp;
 use Path::Class;
-use HTML::Widget::Element::DoubleSelect;
+use List::Util qw(first);
 
 sub mk_compclass {
-    my ( $self, $helper, $attrs, $schema) = @_;
+    my ( $self, $helper, $schema, $m2m, $bridges ) = @_;
     my @classes = map {
-        $attrs->{many_to_many_relation_table}{$schema->class($_)->table} ? () : $_
-    } @{$attrs->{classes}};
-
+        $bridges->{ $_ } ? () : $_
+    } $schema->sources;
     my $dir = dir( $helper->{dir}, 'root' );
     $helper->mk_dir($dir);
 
@@ -32,38 +31,91 @@
     $helper->render_file( restricted => file( $dir, 'restricted.tt' ) );
     $helper->mk_file( file( $dir, 'wrapper.tt' ), $helper->get_file( __PACKAGE__, 'wrapper' ) );
     $helper->mk_file( file( $dir, 'login.tt' ), $helper->get_file( __PACKAGE__, 'login' ) );
+    $helper->mk_file( file( $dir, 'pager.tt' ), $helper->get_file( __PACKAGE__, 'pager' ) );
+#    $helper->mk_file( file( $dir, 'destroy.tt' ), $helper->get_file( __PACKAGE__, 'destroy' ) );
     my $staticdir = dir( $helper->{dir}, 'root', 'static' );
     $helper->mk_dir( $staticdir );
     $helper->render_file( style => file( $staticdir, 'pagingandsort.css' ) );
+    $helper->render_file( formfu_style => file( $staticdir, 'formfu.css' ) );
 
     # javascript
-    $helper->mk_file( file( $staticdir, 'doubleselect.js' ),
-        HTML::Widget::Element::DoubleSelect->js_lib );
+#    $helper->mk_file( file( $staticdir, 'doubleselect.js' ),
+#        HTML::Widget::Element::DoubleSelect->js_lib );
     
     # templates
     for my $class (@classes){
-        my $classdir = dir( $helper->{dir}, 'root', $class );
+        my $classdir = dir( $helper->{dir}, 'root', lc $class );
         $helper->mk_dir( $classdir );
         $helper->mk_file( file( $classdir, $_ . '.tt'), $helper->get_file( __PACKAGE__, $_ ) )
-        for qw/edit destroy pager/;
-        my @fields;
-        my @field_configs;
-#        warn "fconf: " . Dumper(@{$attrs->{config}{$class}{fields}});
-        for my $fconf ( @{$attrs->{config}{$class}} ){
-            if( $fconf->{widget_element} and !( $fconf->{widget_element}[0] eq 'Password') ) {
-                push @fields, $fconf->{name};
-                push @field_configs, $fconf;
-            }
-        }
-        my $fields = join "', '", @fields;
-        $helper->{fields} = "[ '$fields' ]";
-        $helper->{field_configs} = \@field_configs;
+        for qw/edit destroy/;
+        $helper->{field_configs} = _get_column_config( $schema, $class, $m2m ) ;
+        my $source = $schema->source($class);
+        $helper->{primary_keys} = $source->primary_columns;
         $helper->render_file( list => file( $classdir, 'list.tt' ));
         $helper->render_file( view => file( $classdir, 'view.tt' ));
     }
     return 1;
 }
+sub _mk_label {
+    my $name = shift;
+    return join ' ', map { ucfirst } split '_', $name;
+}
 
+sub _get_column_config {
+    my( $schema, $class, $m2m ) = @_;
+    my @configs;
+    my $source = $schema->source($class);
+    my %bridge_cols;
+    for my $rel ( $source->relationships ) {
+        my $info = $source->relationship_info($rel);
+        $bridge_cols{$_} = 1 for  _get_self_cols( $info->{cond} );
+        $m2m->{$class} and next if first { $_->[1] eq $rel } @{$m2m->{$class}};
+        my $config = {
+            name => $rel,
+            label => _mk_label( $rel ),
+        };
+        $config->{multiple} = 1 if $info->{attrs}{accessor} eq 'multi';
+        push @configs, $config;
+    }
+    for my $column ( $source->columns ) {
+        next if $bridge_cols{$column};
+        push @configs, {
+            name => $column,
+            label => _mk_label( $column ),
+        };
+    }
+    if( $m2m->{$class} ) {
+        for my $m ( @{$m2m->{$class}} ){
+            push @configs, {
+                name => $m->[0],
+                label => _mk_label( $m->[0] ),
+                multiple => 1,
+            };
+        }
+    }
+    return \@configs;
+}
+
+sub _get_self_cols{
+    my $cond = shift;
+    my @cols;
+    if ( ref $cond eq 'ARRAY' ){
+        for my $c1 ( @$cond ){
+            push @cols, get_self_cols( $c1 );
+        }
+    }
+    elsif ( ref $cond eq 'HASH' ){
+        for my $key ( values %{$cond} ){
+            if( $key =~ /self\.(.*)/ ){
+                push @cols, $1;
+            }
+        }
+    }
+    return @cols;
+}
+
+
+
 1; # Magic true value required at end of module
 __DATA__
 
@@ -74,7 +126,7 @@
 <table>
 <tr>
 <+ FOR column = field_configs +>
-<+- IF column.widget_element.1 == 'multiple' -+>
+<+- IF column.multiple -+>
 <th> <+ column.name +> </th>
 <+ ELSE +>
 <th> [% order_by_column_link('<+ column.name +>', '<+ column.label+>') %] </th>
@@ -85,7 +137,7 @@
     <tr>
     <+ FOR column = field_configs +>
     <td>
-    <+ IF column.widget_element.1 == 'multiple' +>
+    <+ IF column.multiple +>
     [% FOR val = row.<+ column.name +>; val; ', '; END %]
     <+ ELSE +>
     [%  row.<+ column.name +> %]
@@ -101,13 +153,13 @@
 </table>
 [% PROCESS pager.tt %]
 <br/>
-<a href="[% c.uri_for( 'add' ) %]">Add</a>
+<a href="[% c.uri_for( 'edit' ) %]">Add</a>
 
 __view__
 [% TAGS <+ +> %]
 <+ FOR column = field_configs +>
 <b><+ column.label +>:</b> 
-    <+ IF column.widget_element.1 == 'multiple' +>
+    <+ IF column.multiple +>
     [% FOR val = item.<+ column.name +>; val; ', '; END %]
     <+ ELSE +>
     [%  item.<+ column.name +> %]
@@ -115,6 +167,8 @@
     <br/>
 <+ END +>
 <hr />
+<a href="[% c.uri_for( 'edit', <+ FOR key = primary_keys +>item.<+ key +>, <+ END +> ) %]">Edit</a>
+<hr />
 <a href="[% c.uri_for( 'list' ) %]">List</a>
 
 
@@ -132,6 +186,7 @@
 <title>[% appname %]</title>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 <link href="[%base%]static/pagingandsort.css" type="text/css" rel="stylesheet"/>
+<link href="[%base%]static/formfu.css" type="text/css" rel="stylesheet"/>
 <script src="[%base%]static/doubleselect.js" type="text/javascript" /></script>
 </head>
 <body>
@@ -149,6 +204,8 @@
 [% widget %]
 <br>
 <a href="[% c.uri_for( 'list' ) %]">List</a>
+<hr>
+[% form.html_table %]
 __pager__
 <div id="pager">
 Results: [% pager.first %] to [% pager.last %] from [% pager.total_entries %]<br />
@@ -490,6 +547,206 @@
 	font-weight: normal;
         font: 12px sans-serif;
 }
+__formfu_style__
+
+fieldset {
+	padding: 1em;
+}
+
+fieldset .button,
+fieldset .checkbox,
+fieldset .contentbutton,
+fieldset .file,
+fieldset .image,
+fieldset .multi,
+fieldset .password,
+fieldset .radio,
+fieldset .reset,
+fieldset .select,
+fieldset .submit,
+fieldset .text,
+fieldset .textarea
+{
+	display: block;
+	clear: left;
+	border: 0;
+	margin: 1px;
+	/* when no label */
+	margin-left: 12em;
+}
+
+fieldset .button.label,
+fieldset .checkbox.label,
+fieldset .contentbutton.label,
+fieldset .file.label,
+fieldset .image.label,
+fieldset .multi.label,
+fieldset .password.label,
+fieldset .radio.label,
+fieldset .reset.label,
+fieldset .select.label,
+fieldset .submit.label,
+fieldset .text.label,
+fieldset .textarea.label
+{
+	margin-left: 1px;
+}
+
+fieldset .error.label {
+	/* border + margin swap values with above, to ensure rows align */
+	border: 1px #fff;
+	margin: 0;
+}
+
+fieldset .error_message {
+       display: block;
+       color: #ff0000;
+}
+
+fieldset .label .error_message {
+       /* padding-left eq label width + padding-right */
+       padding-left: 12em;
+}
+
+fieldset .error input,
+fieldset .error textarea,
+fieldset .error select {
+       background-color: #ffdddd;
+}
+
+fieldset .button label,
+fieldset .contentbutton label,
+fieldset .checkbox label,
+fieldset .file label,
+fieldset .image label,
+fieldset .multi label,
+fieldset .password label,
+fieldset .radio label,
+fieldset .radiogroup label,
+fieldset .select label,
+fieldset .text label,
+fieldset .textarea label
+{
+	display: inline;
+	float: left;
+	width: 11em;
+	text-align: right;
+	padding-right: 1em;
+}
+
+fieldset .radiogroup span label {
+	/* undo the above style */
+	float: none;
+	width: auto;
+	text-align: left;
+	padding-right: 0;
+}
+
+fieldset .multi .elements {
+	display: block;
+	float: left;
+}
+
+fieldset .multi .elements label {
+	display: block;
+	width: auto;
+	padding-right: 0.25em;
+}
+
+fieldset .multi input,
+fieldset .multi select {
+	display: block;
+	float: left;
+	margin-right: 0.5em;
+}
+
+fieldset.checkboxgroup,
+fieldset.radiogroup
+{
+	margin: 0;
+	margin-left: 12em;
+	padding: 0;
+	width: auto;
+}
+
+fieldset.radiogroup.label {
+	border: 0;
+	margin-left: 0em;
+}
+
+fieldset .comment .comment {
+	/* when no label */
+	display: block;
+	margin-left: 0em;
+}
+
+fieldset .label .comment {
+	display: block;
+	margin-left: 12em;
+}
+
+/*** Alternative Layouts ***/
+
+fieldset .notes {
+	float: right;
+	width: 30%;
+	border: 1px dotted;
+}
+
+fieldset .multi.vertical input,
+fieldset.checkboxgroup.vertical input,
+fieldset.radiogroup.vertical input
+{
+	display: block;
+	float: left;
+	clear: left;
+}
+
+fieldset .multi.vertical select {
+	display: block;
+	float: left;
+	clear: right;
+}
+
+fieldset.checkboxgroup.vertical label,
+fieldset.radiogroup.vertical label
+{
+	display: block;
+	clear: right;
+}
+
+fieldset.radiogroup.vertical .subgroup {
+	float: left;
+}
+
+fieldset .fullwidth label
+{
+	display: block;
+	float: left;
+	width: auto;
+	text-align: left;
+}
+
+fieldset .fullwidth .error_message {
+       padding-left: 0em;
+}
+
+fieldset .fullwidth textarea
+{
+	display: block;
+	clear: left;
+	width: 30em;
+}
+
+fieldset .dojoeditor2 .RichTextEditable { /* Dojo::Editor2 */
+	display: inline;
+	float: left;
+	background-color: #ffc;
+	padding-bottom: 0.1em;
+}
+
+
+
 __END__
 
 =head1 NAME

Modified: trunk/Catalyst-Example-InstantCRUD/script/instantcrud.pl
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/script/instantcrud.pl	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/script/instantcrud.pl	2008-09-18 19:37:27 UTC (rev 8434)
@@ -6,13 +6,14 @@
 use strict;
 use Getopt::Long;
 use Pod::Usage;
-use YAML 'LoadFile';
 use File::Spec;
 use File::Slurp;
 use Catalyst::Helper::InstantCRUD;
-use Catalyst::Example::InstantCRUD::Utils;
 use Catalyst::Utils;
 use Data::Dumper;
+use DBIx::Class::Schema::Loader qw/ make_schema_at /;
+use DBIx::Class::Schema::Loader::RelBuilder;
+use List::Util qw(first);
 
 my $help     = 0;
 my $adv_help = 0;
@@ -61,19 +62,6 @@
 
 pod2usage($adv_help ? 1 : 2) if $help || $adv_help || !$appname;
 
-{
-    require DBIx::Class::Schema::Loader;
-    no strict 'refs';
-    @{"$schema_name\::ISA"} = qw/DBIx::Class::Schema::Loader/;
-    $schema_name->loader_options(relationships => 1, exclude => qr/^sqlite_sequence$/);
-}
-
-my $schema = $schema_name->connect($dsn, $duser, $dpassword);
-
-my $attrs = Catalyst::Example::InstantCRUD::Utils->load_schema($schema,
-    auth => \%auth, authz => \%authz, noauth => !$auth,
-);
-      
 # Application
 my $helper = Catalyst::Helper::InstantCRUD->new( {
     '.newfiles'   => !$nonew,
@@ -81,8 +69,8 @@
     'short'       => $short,
     'model_name'  => $model_name,
     'schema_name' => $schema_name,
-    'auth'        => $attrs->{auth},
-    'authz'       => $attrs->{authz},
+    'auth'        => \%auth,
+    'authz'       => \%authz,
 } );
 
 pod2usage(1) unless $helper->mk_app( $appname );
@@ -91,22 +79,110 @@
 $appdir =~ s/::/-/g;
 local $FindBin::Bin = File::Spec->catdir($appdir, 'script');
 
+make_schema_at(
+    $appname . '::' . $schema_name,
+    { 
+#        debug => 1, 
+        dump_directory => "$appdir/lib", 
+        use_namespaces => 1,
+        default_resultset_class => '+DBIx::Class::ResultSet::RecursiveUpdate', 
+    },
+    [ $dsn, $duser, $dpassword ],
+);
+
+{
+    no strict 'refs';
+    @{"$schema_name\::ISA"} = qw/DBIx::Class::Schema::Loader/;
+    $schema_name->loader_options(relationships => 1, exclude => qr/^sqlite_sequence$/);
+}
+
+my $schema = $schema_name->connect($dsn, $duser, $dpassword);
+
+my ( $m2m, $bridges ) = guess_m2m( $schema );
+for my $result_class ( keys %$m2m ){
+    my $result_source = $schema->source( $result_class );
+    my $overload_method = first { $_ =~ /name/i } $result_source->columns;
+    $overload_method ||= 'id';
+    my @path = split /::/ , $appname . '::' . $schema_name;
+    my $file = File::Spec->rel2abs( File::Spec->catfile($appdir, 'lib', @path, 'Result', $result_class . '.pm' ) );
+    my $content = File::Slurp::slurp( $file );
+    my $addition = q/use overload '""' => sub {$_[0]->/ . $overload_method . "}, fallback => 1;\n";
+    for my $m ( @{$m2m->{$result_class}} ){
+        my $a0 = $m->[0];
+        my $a1 = $m->[1];
+        my $a2 = $m->[2];
+        $addition .= "__PACKAGE__->many_to_many('$a0', '$a1' => '$a2');\n";
+    }
+    $content =~ s/1;\s*/$addition\n1;/;
+    File::Slurp::write_file( $file, $content );
+}
+
 # Controllers
 $helper->mk_component ( $appname, 'controller', 'InstantCRUD', 'InstantCRUD',
-  $schema, $attrs,
+  $schema, $m2m,
 );
 
 # Model
-$helper->mk_component ( $appname, 'model', $model_name, 'InstantCRUD', 
-  $schema_name, $dsn, $duser, $dpassword, {}, $attrs
+$helper->mk_component ( $appname, 'model', $model_name, 'DBIC::Schema', 
+  $appname . '::' . $schema_name, $dsn, $duser, $dpassword, 
 );
 
 # View and Templates
-$helper->mk_component ( $appname, 'view', 'TT', 'InstantCRUD', $attrs, $schema );
+$helper->mk_component ( $appname, 'view', 'TT', 'InstantCRUD', $schema, $m2m, $bridges );
 
-#my $tfile =  File::Spec->catdir ( $appdir, 't', 'controller_InstantCRUD.t' );
-#unlink $tfile or die "Cannot remove $tfile - the wrong test file: $!";
+sub guess_m2m {
+    my $schema = shift;
+    my %m2m;
+    my %bridges;
+    my $inflector       = DBIx::Class::Schema::Loader::RelBuilder->new;
 
+    CLASS:
+    for my $s ( $schema->sources ) {
+        my $source = $schema->source($s);
+        my $c      = $schema->class($s);
+        my @relationships = $c->relationships;
+        my @cols = $source->columns;
+        next if scalar @relationships != 2;
+        next if scalar @cols!= 2;
+        my @rclasses;
+        for my $rel (@relationships) {
+            my $info = $source->relationship_info($rel);
+            next CLASS if $info->{attrs}{accessor} eq 'multi';
+            my $rclass_name = $info->{class};
+            $rclass_name =~ /([^:]*)$/;
+            $rclass_name = $1;
+            my $rclass = $schema->class( $rclass_name );
+            my $rsource = $schema->source( $rclass_name );
+            my $found;
+            for my $rrel ( $rclass->relationships ){
+                my $rinfo = $rsource->relationship_info($rrel);
+                my $rrclass_name = $rinfo->{class};
+                $rrclass_name =~ /([^:]*)$/;
+                $rrclass_name = $1;
+                if( $rrclass_name eq $s ){
+                    $found = $rrel;
+                    last;
+                }
+            }
+            next CLASS if not $found;
+            push @rclasses, { rclass => $rclass_name, bridge => [ $found, $rel ] };
+        }
+        push @{$m2m{ $rclasses[0]->{rclass} }}, [ 
+            $inflector->_inflect_plural( $rclasses[1]->{bridge}[1] ), 
+            $rclasses[1]->{bridge}[0], 
+            $rclasses[1]->{bridge}[1] 
+        ];
+        push @{$m2m{ $rclasses[1]->{rclass} }}, [ 
+            $inflector->_inflect_plural( $rclasses[0]->{bridge}[1] ), 
+            $rclasses[0]->{bridge}[0], 
+            $rclasses[0]->{bridge}[1] 
+        ];
+        $bridges{$s} = 1;
+    }
+    return \%m2m, \%bridges;
+}
+    
+
 1;
 
 __END__

Modified: trunk/Catalyst-Example-InstantCRUD/t/00.createapp.t
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/t/00.createapp.t	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/t/00.createapp.t	2008-09-18 19:37:27 UTC (rev 8434)
@@ -1,6 +1,6 @@
 use strict;
 use warnings;
-use Test::More tests => 1;
+use Test::More tests => 2;
 use Path::Class;
 use File::Path;
 use File::Copy;
@@ -25,5 +25,6 @@
 `perl -I$libdir ../../script/instantcrud.pl -name=My::App -dsn='dbi:SQLite:dbname=$testfile' -noauth`;
 chdir $currdir;
 
-my $schemafile = file(qw/ t tmp My-App lib My App DBSchema.pm/);
-ok( -f $schemafile, 'DBSchema creation');
+ok( -f file(qw/ t tmp My-App lib My App DBSchema.pm/), 'DBSchema creation');
+ok( -f file( qw/ t tmp My-App lib My App Controller Usr.pm / ), 'Controller for "User" created');
+

Modified: trunk/Catalyst-Example-InstantCRUD/t/10.apptest.t
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/t/10.apptest.t	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/t/10.apptest.t	2008-09-18 19:37:27 UTC (rev 8434)
@@ -33,6 +33,7 @@
     fields      => {
         intfield => '3',
         varfield => 'Changed varchar field',
+        charfield => 'a',
     }
 );
 $mech->follow_link_ok({text => 'Firsttable'}, "Click on firsttable");

Modified: trunk/Catalyst-Example-InstantCRUD/t/21.test_dvdzbr.t
===================================================================
--- trunk/Catalyst-Example-InstantCRUD/t/21.test_dvdzbr.t	2008-09-18 13:11:29 UTC (rev 8433)
+++ trunk/Catalyst-Example-InstantCRUD/t/21.test_dvdzbr.t	2008-09-18 19:37:27 UTC (rev 8434)
@@ -10,30 +10,30 @@
 if ($@){
     plan skip_all => "Test::WWW::Mechanize::Catalyst required for testing application";
 }else{
-    plan tests => 28;
+    plan tests => 20;
     #plan tests => 'no_plan';
 }
 
 my $mech = Test::WWW::Mechanize::Catalyst->new;
 $mech->get_ok("http://localhost/", "Application Running");
 
-$mech->content_lacks("dvdtag", "Do not list the relation tables");
-$mech->content_lacks("user_role", "Do not list the relation tables");
-
-$mech->follow_link_ok({text => 'Restricted Area'}, "Go to restricted area");
-
-$mech->content_like(qr/Username.*Password/, "Login Requested");
-$mech->submit_form(
-    form_number => 1,
-    fields      => {
-        username => 'jgda',
-	password => 'jonas',
-    },
-);
-
-$mech->follow_link_ok({text => 'Restricted Area'}, "Go to restricted area");
-$mech->content_contains("This is the restricted area", "Yes, we are logged in");
-
+# $mech->content_lacks("dvdtag", "Do not list the relation tables");
+# $mech->content_lacks("user_role", "Do not list the relation tables");
+# 
+# $mech->follow_link_ok({text => 'Restricted Area'}, "Go to restricted area");
+# 
+# $mech->content_like(qr/Username.*Password/, "Login Requested");
+# $mech->submit_form(
+#     form_number => 1,
+#     fields      => {
+#         username => 'jgda',
+# 	password => 'jonas',
+#     },
+# );
+# 
+# $mech->follow_link_ok({text => 'Restricted Area'}, "Go to restricted area");
+# $mech->content_contains("This is the restricted area", "Yes, we are logged in");
+ 
 $mech->follow_link_ok({text => 'Tag'}, "Click on tag");
 $mech->follow_link_ok({text => 'Add'}, "Let's add a tag :)");
 $mech->submit_form(
@@ -47,7 +47,7 @@
 $mech->follow_link_ok({text => 'Name'}, "Let's sort them");
 $mech->content_contains("TestTag", "Yes, our tag is listed");
 
-$mech->get_ok("/user/add", "Adding a User");
+$mech->get_ok("/user/edit", "Adding a User");
 $mech->submit_form(
     form_number => 1,
     fields      => {
@@ -60,44 +60,44 @@
         #user_roles => 0,
     },
 );
-$mech->content_contains("Confirm the password", "Password constraint");
-
-$mech->submit_form(
-    form_number => 1,
-    fields      => {
-        name => 'Zbigniew Lukasiak',
-        username => 'zby',
-	password => 'zby',
-	password_2 => 'zbyyyy',
- 
-        #dvd_owners => 0,
-        #dvd_current_owners => 0,
-        #user_roles => 0,
-    }
-);
-$mech->content_contains("Passwords must match", "Password constraint");
-
-$mech->submit_form(
-    form_number => 1,
-    fields      => {
-        name => 'Zbigniew Lukasiak',
-        username => 'zby',
-	password => 'zby',
-	password_2 => 'zby',
-
-        #dvd_owners => 0,
-        #dvd_current_owners => 0,
-        #user_roles => 0,
-    }
-);
+# $mech->content_contains("Confirm the password", "Password constraint");
+# 
+# $mech->submit_form(
+#     form_number => 1,
+#     fields      => {
+#         name => 'Zbigniew Lukasiak',
+#         username => 'zby',
+# 	password => 'zby',
+# 	password_2 => 'zbyyyy',
+#  
+#         #dvd_owners => 0,
+#         #dvd_current_owners => 0,
+#         #user_roles => 0,
+#     }
+# );
+# $mech->content_contains("Passwords must match", "Password constraint");
+# 
+# $mech->submit_form(
+#     form_number => 1,
+#     fields      => {
+#         name => 'Zbigniew Lukasiak',
+#         username => 'zby',
+# 	password => 'zby',
+# 	password_2 => 'zby',
+# 
+#         #dvd_owners => 0,
+#         #dvd_current_owners => 0,
+#         #user_roles => 0,
+#     }
+# );
 $mech->content_contains('Zbigniew Lukasiak', "User added");
 $mech->get_ok("/user/list", "Listing Users");
 $mech->content_contains("Zbigniew Lukasiak", "User listed");
 
-$mech->get_ok("/dvd/add", "Adding a DVD with a related Tag");
+$mech->get_ok("/dvd/edit", "Adding a DVD with a related Tag");
 
 # Hack to simulate the selection of a value in the double select
-$mech->form_number(1)->push_input(option => {name => 'tags', value => '1' });
+#$mech->form_number(1)->push_input(option => {name => 'tags', value => '1' });
 
 $mech->submit_form(
     form_number => 1,
@@ -112,7 +112,6 @@
 	imdb_id => 133,
     }
 );
-warn $mech->content();
 
 $mech->content_contains('Jurassic Park II', "DVD added");
 $mech->content_like(qr/Tags[^A]+Action/, "DVD added with Tag");




More information about the Catalyst-commits mailing list