package RapidApp::Module::DatStor::Column;
# ** This class used to be RapidApp::Column **
use strict;
use warnings;
use RapidApp::Util qw(:all);
our @gridColParams= qw(
name sortable hidden header dataIndex width editor menuDisabled tpl xtype
id no_column no_multifilter no_quick_search extra_meta_data css listeners
filter field_cnf rel_combo_field_cnf field_cmp_config render_fn renderer
allow_add allow_edit allow_view query_id_use_column query_search_use_column
trueText falseText menu_select_editor render_column multifilter_type summary_functions
no_summary allow_batchedit format align is_nullable documentation
);
our @attrs= ( @gridColParams, qw(
data_type required_fetch_columns read_raw_munger update_munger
field_readonly field_readonly_config field_config no_fetch broad_data_type
quick_search_exact_only enum_value_hash search_operator_strf
) );
our %triggers= (
render_fn => '_set_render_fn',
renderer => '_set_renderer',
menu_select_editor => '_set_menu_select_editor',
);
eval('sub '.$_.' {'
.(exists($triggers{$_})
? 'if (scalar @_ > 1) { my $old= $_[0]->{'.$_.'}; $_[0]->{'.$_.'} = $_[1]; $_[0]->'.$triggers{$_}.'($_[0]->{'.$_.'}, $old); }'
: '$_[0]->{'.$_.'} = $_[1] if scalar @_ > 1;'
).'$_[0]->{'.$_.'}
}') for @attrs;
our %defaults= (
sortable => '\1',
hidden => '\0',
header => '$self->{name}',
dataIndex => '$self->{name}',
width => '70',
no_column => '\0',
no_multifilter => '\0',
no_quick_search => '\0',
field_readonly => '0',
required_fetch_columns => '[]',
field_readonly_config => '{}',
field_config => '{}',
);
eval 'sub apply_defaults {
my $self= shift;
'.join(';', map { 'exists $self->{'.$_.'} or $self->{'.$_.'}= '.$defaults{$_} } keys %defaults).'
}';
sub _set_render_fn {
my ($self,$new,$old) = @_;
die 'render_fn is depricated, please use renderer instead.';
return unless ($new);
# renderer takes priority over render_fn
return if (defined $self->renderer);
$self->xtype('templatecolumn');
$self->tpl('{[' . $new . '(values.' . $self->name . ',values)]}');
}
sub _set_renderer {
my ($self,$new,$old) = @_;
return unless ($new);
$self->xtype(undef);
$self->tpl(undef);
return unless (defined $new and not blessed $new);
$self->{renderer}= jsfunc($new);
}
sub _set_menu_select_editor {
my ($self,$new,$old) = @_;
return unless ($new);
my %val_to_disp = ();
my @value_list = ();
foreach my $sel (uniq(@{$new->{selections}})) {
push @value_list, [$sel->{value},$sel->{text},$sel->{iconCls}];
if(defined $sel->{value} and defined $sel->{text}) {
$val_to_disp{$sel->{value}} = $sel->{text};
$val_to_disp{$sel->{value}} = '
' . $sel->{text} . '
'
if($sel->{iconCls});
$val_to_disp{$sel->{value}} = '
'
if($sel->{iconCls} and jstrue($new->{render_icon_only}));
};
}
my $first_val;
$first_val = $value_list[0]->[0] if (defined $value_list[0]);
my $mapjs = encode_json(\%val_to_disp);
my $js = 'function(v){' .
'var val_map = ' . $mapjs . ';' .
'if(typeof val_map[v] !== "undefined") { return val_map[v]; }' .
'return v;' .
'}';
$self->{renderer} = jsfunc($js,$self->{renderer});
# If there is already a 'value' property set in editor save it to preserve it (see below):
my $orig_value = ref($self->{editor}) eq 'HASH' ? $self->{editor}->{value} : undef;
# Update: removed extra, not-needed check of 'allow_edit' param (fixes Github Issue #35)
my $mode = $new->{mode} || 'combo';
if($mode eq 'combo') {
$self->{editor} = {
xtype => 'ra-icon-combo',
allowBlank => \0,
value_list => \@value_list,
};
}
elsif($mode eq 'menu') {
$self->{editor} = {
xtype => 'menu-field',
menuOnShow => \1,
value_list => \@value_list,
value => $first_val,
minHeight => 10,
minWidth => 10
};
$self->{editor}->{header} = $new->{header} || $self->header;
delete $self->{editor}->{header} unless (
defined $self->{editor}->{header} and
$self->{editor}->{header} ne ''
);
}
elsif($mode eq 'cycle') {
$self->{editor} = {
xtype => 'cycle-field',
cycleOnShow => \1,
value_list => \@value_list,
value => $first_val,
minHeight => 10,
minWidth => 10
};
}
else {
die "menu_select_editor: Invalid mode '$mode' - must be 'combo', 'menu' or 'cycle'"
}
# restore the original 'value' if it was already defined (see above)
$self->{editor}->{value} = $orig_value if (defined $orig_value);
$self->{editor}->{width} = $new->{width} if ($new->{width});
}
our %attrKeySet= map { $_ => 1 } @attrs;
our %gridColParamKeySet= map { $_ => 1 } @gridColParams;
sub new {
my $class= shift;
my $self= bless { (ref($_[0]) eq 'HASH')? %{$_[0]} : @_ }, $class;
$self->{renderer} = jsfunc($self->{renderer}) if (defined $self->{renderer} and not blessed $self->{renderer});
for (keys %$self) {
$attrKeySet{$_} || die("No such attribute: $class\::$_");
my $t= $triggers{$_};
$t and $self->$t($self->{$_});
}
$self->apply_defaults;
return $self;
}
sub apply_attributes {
my $self = shift;
my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
# Ignore attribute names which start with underscore (_) -- backcompat fix for special
# flag/keys added in 1.1000 for the "allow_*" refactor
delete $new{$_} for (grep { $_ =~ /^\_/ } keys %new);
foreach my $attr (@attrs) {
next unless (exists $new{$attr});
$self->$attr($new{$attr});
delete $new{$attr};
}
#There should be nothing left over in %new:
if (scalar(keys %new) > 0) {
#die "invalid attributes (" . join(',',keys %new) . ") passed to apply_attributes";
#use Data::Dumper;
die "invalid attributes (" . join(',',keys %new) . ") passed to apply_attributes :\n" . Dumper(\%new);
}
$self->_normalize_allow_add_edit
}
sub applyIf_attributes {
my $self = shift;
my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
# Ignore attribute names which start with underscore (_) -- backcompat fix for special
# flag/keys added in 1.1000 for the "allow_*" refactor
delete $new{$_} for (grep { $_ =~ /^\_/ } keys %new);
foreach my $attr (@attrs) {
next unless (exists $new{$attr});
$self->$attr($new{$attr}) unless defined $self->{$attr}; # <-- only set attrs that aren't already set
delete $new{$attr};
}
#There should be nothing left over in %new:
if (scalar(keys %new) > 0) {
#die "invalid attributes (" . join(',',keys %new) . ") passed to apply_attributes";
#use Data::Dumper;
die "invalid attributes (" . join(',',keys %new) . ") passed to apply_attributes :\n" . Dumper(\%new);
}
$self->_normalize_allow_add_edit
}
## -----
##
## This just ensures allow_add/allow_edit/allow_view/no_column are always populated, with
## values which are already implied by the existing rules. This just makes it easier to
## check later, avoiding all the defined or not checks, etc
##
sub _normalize_allow_add_edit {
my $self = shift;
$self->no_column(${$self->{no_column}}) if (ref $self->{no_column}); # consistent 0/1
$self->no_column(0) unless ($self->no_column);
$self->_normalize_allow_edit;
$self->allow_edit(${$self->{allow_edit}}) if (ref $self->{allow_edit});
$self->_normalize_allow_add;
$self->allow_add(${$self->{allow_add}}) if (ref $self->{allow_add});
$self->_normalize_allow_view;
$self->allow_view(${$self->{allow_view}}) if (ref $self->{allow_view});
}
sub _normalize_allow_edit {
my $self = shift;
# if its already set - and turned off - we're done:
return if (exists $self->{allow_edit} && !jstrue($self->{allow_edit}));
# remember if allow_edit was *not* already set, because this effects the default for allow_add
$self->{_allow_edit_init_unset} = 1 unless (exists $self->{allow_edit});
# if its true (or not yet set, which implies true) test for any
# conditions which prevent it from being true
my $no_edit = (
! $self->{editor}
|| ( $self->no_column && $self->{_allow_edit_init_unset} )
);
$self->allow_edit( $no_edit ? 0 : 1 )
}
sub _normalize_allow_add {
my $self = shift;
# if its already set - and turned off - we're done:
return if (exists $self->{allow_add} && !jstrue($self->{allow_add}));
$self->{_allow_add_init_unset} = 1 unless (exists $self->{allow_add});
# Go with allow_edit when it's explicitly false and we weren't set
return $self->allow_add(0) if(
! exists $self->{allow_add}
&& ! $self->allow_edit
&& ! $self->{_allow_edit_init_unset}
);
# if its true (or not yet set, which implies true) test for any
# conditions which prevent it from being true
my $no_add = (
! $self->{editor}
|| ( $self->no_column && ! $self->allow_edit && $self->{_allow_add_init_unset} )
);
$self->allow_add( $no_add ? 0 : 1 )
}
sub _normalize_allow_view {
my $self = shift;
# if its already set - and turned off - we're done:
return if (exists $self->{allow_view} && !jstrue($self->{allow_view}));
# remember if allow_edit was *not* already set, because this effects the default for allow_add
$self->{_allow_view_init_unset} = 1 unless (exists $self->{allow_view});
# If allow_edit was expressly set to false:
#my $deny_edit = (! $self->allow_edit && ! $self->{_allow_edit_init_unset});
# if its true (or not yet set, which implies true) test for any
# conditions which prevent it from being true
my $no_view = (
$self->no_column && $self->{_allow_view_init_unset}
);
$self->allow_view( $no_view ? 0 : 1 )
}
##
## -----
sub get_grid_config {
my $self = shift;
return { map { defined($self->{$_})? ($_ => $self->{$_}) : () } @gridColParams };
}
sub apply_field_readonly_config {
my $self= shift;
%{ $self->{field_readonly_config} }= %{ $self->{field_readonly_config} }, (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
}
sub get_field_config_readonly_param {
my $self= shift;
return $self->{field_readonly_config}{$_[0]};
}
sub has_field_config_readonly_param {
my $self= shift;
return exists $self->{field_readonly_config}{$_[0]};
}
sub has_no_field_readonly_config {
my $self= shift;
return 0 == (keys %{ $self->{field_readonly_config} });
}
sub delete_field_readonly_config_param {
my $self= shift;
delete $self->{field_readonly_config}{$_[0]};
}
sub apply_field_config {
my $self= shift;
%{ $self->{field_config} }= %{ $self->{field_config} }, (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
}
sub get_field_config_param {
my $self= shift;
return $self->{field_config}{$_[0]};
}
sub has_field_config_param {
my $self= shift;
return exists $self->{field_config}{$_[0]};
}
sub has_no_field_config {
my $self= shift;
return 0 == (keys %{ $self->{field_config} });
}
sub delete_field_config_param {
my $self= shift;
delete $self->{field_config}{$_[0]};
}
sub get_field_config {
my $self = shift;
my $config = $self->field_config;
$config = $self->editor if ($self->editor);
my $cnf = {
name => $self->name,
%$config
};
$cnf = { %$cnf, %{$self->field_readonly_config} } if ($self->field_readonly);
$self->field_cmp_config($cnf);
return $cnf;
}
1;