How do I override compare_fields when using Rose::HTML::Form - perl

So I'm trying use Rose::HTML::Form and I want my fields to appear based on 'rank' rather than by name (the default) .
I've written a comparator subroutine:
sub _order_by_rank {
my ($self, $one, $two) = #_;
return $one->rank <=> $two->rank;
};
and referenced it in my form constructor:
Rose::HTML::Form->new(method => 'post', compare_fields => \&_order_by_rank);
But I am then left with:
Can't call method "name" on unblessed reference at /usr/lib/perl5/site_perl/5.8.8/Rose/HTML/Form/Field/Collection.pm line 405.
It seems to call the comparator before I've added anything.
After constructing the form object, I add some fields and then call init_fields:
$form->add_fields(
id => { type => 'hidden', value => "", rank => 0 },
number => { type => 'int', size => 4, required => 1, label => 'Plant Number', rank => 1 },
name => { type => 'text', size => 25, required => 1, label => 'Plant Name', rank => 2 },
...
);
$form->init_fields;
According to the documentation this is something people usually do. What it doesn't explain is how to do it.
Hopefully someone can explain this to me before I have to buy a new keyboard :)

From the documentation it looks as though, rather than passing in a subroutine reference, you need to subclass Rose::HTML::Form and override the compare_fields method.
The default comparison method is Rose::HTML::Form::compare_fields. You have to create subclasses if you want different sorting methods for different forms.
It would help me to explain further if you showed your full code.

Related

Getting initial_filter to work with subpanel's Select button

In Suitecrm have a subpanel and when clicking 'Select' I want it to show only a specific set of records.
I have a subpanel definition file that looks like this and tried all possible variations in the section "initial_filter" but when the pop-up comes up it shows all of the records
$layout_defs["un_inventory"]["subpanel_setup"]['un_inventory_leads_1'] = array (
'order' => 0,
'module' => 'Leads',
'subpanel_name' => 'default',
'sort_order' => 'asc',
'sort_by' => 'id',
'title_key' => 'LBL_UN_INVENTORY_LEADS_1_FROM_LEADS_TITLE',
//'get_subpanel_data' => 'un_inventory_leads_1',
'get_subpanel_data' => 'function:get_parent_leads',
'function_parameters' =>
array('import_function_file' => 'custom/modules/Leads/func/get_parent_leads_file.php',),
'top_buttons' =>
array (
0 =>
array (
'widget_class' => 'SubPanelTopButtonQuickCreate',
),
1 =>
array (
//'widget_class' => 'SubPanelTopSelectButton',
'widget_class' => 'SubPanelTopSelectButtonParentProjectLeads',
'mode' => 'MultiSelect',
// 'initial_filter_fields' => "&first_name_advanced=hello",
// 'initial_filter' => array('parent_project_id_c_advanced' => array('83b30b20-83a6-8099-b3b9-5d4a491888e6')),
// 'initial_filter' => array('parent_project_id_c_advanced' => '83b30b20-83a6-8099-b3b9-5d4a491888e6'),
// 'initial_filter' => array('account_type_advanced' => array('Student')),
// 'initial_filter' => '&parent_project_id_c=83b30b20-83a6-8099-b3b9-5d4a491888e6',
),
),
);
There are many example of how this is done for 'relate' fields in editview, but not so much for subpanel's such as above, I'm pretty sure many would find this valuable.
Solution would probably be applicable to sugarcrm CE also
Solution is to override the default TopButtonSelect class and hardcode the $initial_filter variable.
So if we hardcode the value like so
$initial_filter.='&parent_project_id_c_advanced='.urlencode("83b30b20-83a6-8099-b3b9-5d4a491888e6");
It will only show records which have the parent_project_id_c field with the value 83b30b20-83a6-8099-b3b9-5d4a491888e6
Hope this helps
Source: http://qc-digital.com/filter-values-shown-when-we-click-on-select-button-inside-a-subpanel/
The above soluton proposed by Robert Sinclair does work, but it creates a dedicated-use situation. If instead you wanted to create a solution that will let you use the changes to have a functional initial_filter for any subpanel, make the following edits.
Thank you to Robert Sinclair and qc-digital.com for not only showing how to make his solution work but also for explaining why the expected method does not work, enabling the edits below to fix the SugarWidget and have it behave as expected.
Two fixes in this post:
a) Have the initial_filter_fields array be checked against the target
module not the host module
b) have the key=>value arrangement in the
layoutdefs work like it does in other situations
Two steps to make this fix work:
Create a custom SugarWidget
Tell the subpanel to use the custom SugarWidget and add the initial_filter_fields array to the TopSelectButton definition
You can then use the custom SugarWidget in any other subpanel and the initial_filter_field will work
Create the custom SugarWidget
mkdir -p custom/include/generic/SugarWidgets/
cp -a include/generic/SugarWidgets/SugarWidgetSubPanelTopSelectButton.php custom/include/generic/SugarWidget/SugarWidgetSubPanelTopSelectButtonWithFilter.php
nano custom/include/generic/SugarWidgets/SugarWidgetSubPanelTopSelectButtonWithFilter.php
Change
class SugarWidgetSubPanelTopSelectButton extends SugarWidgetSubPanelTopButton
To
class SugarWidgetSubPanelTopSelectButtonWithFilter extends SugarWidgetSubPanelTopButton
Change
if (isset($widget_data['initial_filter_fields'])) {
if (is_array($widget_data['initial_filter_fields'])) {
foreach ($widget_data['initial_filter_fields'] as $value=>$alias) {
if (isset($focus->$value) and !empty($focus->$value)) {
$initial_filter.="&".$alias . '='.urlencode($value);
}
}
}
}
To
/*
* Edited Below to use the target module not the host module
* and to use the normal key=>value arrangement
if (isset($widget_data['initial_filter_fields'])) {
if (is_array($widget_data['initial_filter_fields'])) {
foreach ($widget_data['initial_filter_fields'] as $value=>$alias) {
if (isset($focus->$value) and !empty($focus->$value)) {
$initial_filter.="&".$alias . '='.urlencode($value);
}
}
}
}
*/
if (isset($widget_data['initial_filter_fields'])) {
if (is_array($widget_data['initial_filter_fields'])) {
foreach ($widget_data['initial_filter_fields'] as $alias=>$value) {
$initial_filter.="&".$alias . '='.urlencode($value);
}
}
}
Tell the subpanel to use the custom SugarWidget (in my case, editing the Events module (FP_events) subpanel I created pointing to Accounts ... edit to suit your situation)
nano custom/Extension/modules/FP_events/Ext/Layoutdefs/fp_events_accounts_1_FP_events.php
Change
array (
'widget_class' => 'SubPanelTopSelectButton',
'mode' => 'MultiSelect',
),
To
array (
'widget_class' => 'SubPanelTopSelectButtonWithFilter',
'mode' => 'MultiSelect',
'initial_filter_fields' => array(
'account_type_advanced' => 'Venue',
),
),
You will now be able to use the initial_filter_fields functionality in any subpanel by specifying the custom SugarWidget and including the initial_filter_fields array definition in the TopSelectButton definition.

HTML::FormHandler access to form field

Is there any way to access the value of form field 'wklloc_id' in the form field options method of field 'prg_id'?
My Form contains (amongst others) these fields:
has_field 'wklloc_id' => ( type => 'Select', label => 'Winkel(locatie)' );
has_field 'prg_id' => ( type => 'Select', empty_select => 'Kies eerst een Winkel(locatie)', label => 'Productgroep' );
At this point my options method for field 'prg_id' contains:
sub options_prg_id
{
my ($self) = shift;
my (#prg_select_list,$productgroep,$productgroepen);
return unless ($self->schema);
$productgroepen = $self->schema->resultset( 'WinkelLocatieProductgroepen' )->search( {}, { bind => [ 2 ] } );
Is is possible to set the value of the bind variable (i.e. 2) to the value of field 'wklloc_id' and how does one do that? Mind you this is needed before any submit.
The value of a select field is set the same way as other fields, i.e. it comes from an init_object, from parameter values, or from a default. For your case, if you want this field to start with the 'value' 2, then just put: default => 2 in your field definition.

HTML::FormHandler sort_column for HTML::FormHandler::Field::Select

The documentation for HTML::FormHandler::Select says that the sort_column option Sets or returns the column or arrayref of columns used in the foreign class for sorting the options labels. I've tried setting it, but it is not setting my options:
has_field 'my_field' => (
type => 'Select',
sort_column => 'label',
required => 1,
);
I've also tried not setting sort_column, since the default is to sort by the label column and that's what I want, but it doesn't seem to work still. Does anyone know how to have HTML::FormHandler sort the values of my select field? Currently the values are being set with an options function:
sub options_my_field {
return [
{
value => 1,
label => 'One',
},
{
value => 2,
label => 'Two',
},
];
}

MooseX::Declare how can I return an ArrayRef from an attribute default method?

I realise this is probably a basic misunderstanding of some part of perl or Moose on my part, but I don't seem to be able to return an ArrayRef from a default method:
has '_directories' => (
is => 'ro',
isa => 'ArrayRef[Str]',
lazy => 1,
init_arg => undef,
default => method {return File::Spec->splitdir($self->relativeDirectory)});
gives:
Attribute (_directories) does not pass the type constraint because:
Validation failed for 'ArrayRef[Str]' with value 3
How do I sort this out?
splitdir returns list, not arrayref. You can construct arrayref from list using [] constructor:
default => method {return [ File::Spec->splitdir($self->relativeDirectory) ] },

How do I best make triggered accessors with defaults in Moose?

I have a situation where I'd like to cache some calculations for use
later. Let's say I have a list of allowed values. Since I'm going to
be checking to see if anything is in that list I'm going to want it as
a hash for efficiency and convenience. Otherwise I'd have to grep.
If I'm using Moose it would be nice if the cache was recalculated each
time the list of allowed values is changed. I can do that with a
trigger easy enough...
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
my %hash = map { $_ => 1 } #{$_[1]};
$_[0]->allowed_values_cache(\%hash);
}
);
has allowed_values_cache => (
is => 'rw',
isa => 'HashRef',
);
And the two will stay in sync...
$obj->allowed_values([qw(up down left right)]);
print keys %{ $obj->allowed_values_cache }; # up down left right
Now let's say I want a default for allowed_values, simple enough
change...
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
my %hash = map { $_ => 1 } #{$_[1]};
$_[0]->allowed_values_cache(\%hash);
},
default => sub {
return [qw(this that whatever)]
},
);
...except setting the default doesn't call the trigger. To get it to
DWIM I need to duplicate the caching.
has allowed_values => (
is => 'rw',
isa => 'ArrayRef',
trigger => sub {
$_[0]->cache_allowed_values($_[1]);
},
default => sub {
my $default = [qw(this that whatever)];
$_[0]->cache_allowed_values($default);
return $default;
},
);
sub cache_allowed_values {
my $self = shift;
my $values = shift;
my %hash = map { $_ => 1 } #$values;
$self->allowed_values_cache(\%hash);
return;
}
The Moose docs are explicit about trigger not getting called when
the default is set, but it gets in the way. I don't like the
duplication there.
Is there a better way to do it?
I was recently faced with this, and after asking on the #moose channel, was told to handle it this way:
Mark cache_allowed_values as a lazy_build, have _build_cache_allowed_values reference the current allowed_values, and put a write-trigger on allowed_values that clears cache_allowed_values.
That way, no matter what order the values are asked for or saved, they'll always be right with the least amount of work.
Example:
has cache_allowed_values => (is => 'ro', lazy_build => 1);
sub _build_cache_allowed_values {
return { map { $_ => 1 } #{shift->allowed_values} };
}
has allowed_values => (
is => 'rw',
trigger => sub { shift->clear_cache_allowed_values },
default => ...,
);
I think you really want allowed_values to be a separate data structure with the efficiency and ordering properties you desire. Since it doesn't look like you care about the ordering, why not:
has 'allowed_values' => (
traits => ['Hash'],
isa => HashRef[Bool],
default => sub { +{} },
handles => {
_add_allowed_value => 'set',
remove_allowed_value => 'delete',
value_is_allowed => 'exists',
allowed_values => 'keys',
},
);
method add_allowed_value(Str $value){
$self->_add_allowed_value( $value, 1 );
}
In general, anything not specific to the class being implemented should probably be implemented elsewhere. Making arrays have faster lookup times is not really the job of whatever class you are writing, so it should be implemented elsewhere, and this class should use that class. (In the simple case, like the hash above, maybe it's OK to ignore this rule. But if it were any more complicated, you would definitely want to factor it out.)
Edit:
If you want the user to think this is a list, how about:
use MooseX::Types::Moose qw(Bool ArrayRef HashRef);
use MooseX::Types -declare => ['ListHash'];
subtype ListHash, as HashRef[Bool];
coerce ListHash, from ArrayRef, via { +{ map { $_ => 1 } #$_ } };
has 'allowed_values' => (
# <same as above>
isa => ListHash,
writer => 'set_allowed_values',
coerce => 1,
);
Now you can set allowed_values like:
my $instance = Class->new( allowed_values => [qw/foo bar/] );
$instance->set_allowed_values([qw/foo bar baz/]);
And access them like:
my #allowed_values = $instance->allowed_values;
... if $instance->value_is_allowed('foo');
And modify them:
$instance->remove_allowed_value('foo');
$instance->add_allowed_value('gorch');
This hides any underlying implementation details from the user.
BTW, is building the hash actually and using it significantly faster than a linear scan over 3 elements?