[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