Bivio::UI::XHTML::ViewShortcuts
# Copyright (c) 2005-2014 bivio Software, Inc. All Rights Reserved.
# $Id$
package Bivio::UI::XHTML::ViewShortcuts;
use strict;
use Bivio::Base 'UIXHTML';
use Bivio::UI::ViewLanguageAUTOLOAD;
my($_FF) = b_use('HTMLWidget.FormField');
my($_W) = b_use('UI.Widget');
my($_AA) = b_use('Action.Acknowledgement');
my($_M) = b_use('Biz.Model');
my($_WF) = b_use('UIHTML.WidgetFactory');
my($_ELFM) = b_use('Biz.ExpandableListFormModel');
my($_HTML_TAGS) = join('|', qw(
A
ABBR
ACRONYM
ADDRESS
APPLET
AREA
B
BASE
BASEFONT
BDO
BIG
BLOCKQUOTE
BODY
BR
BUTTON
CANVAS
CAPTION
CENTER
CITE
CODE
COL
COLGROUP
DD
DEL
DFN
DIR
DIV
DL
DT
EM
FIELDSET
FONT
FORM
FRAME
FRAMESET
H1
H2
H3
H4
H5
H6
HEAD
HR
HTML
I
IFRAME
IMG
INPUT
INS
ISINDEX
KBD
LABEL
LEGEND
LI
LINK
MAP
MENU
META
NAV
NOFRAMES
NOSCRIPT
OBJECT
OL
OPTGROUP
OPTION
P
PARAM
PRE
Q
S
SAMP
SCRIPT
SELECT
SMALL
SPAN
STRIKE
STRONG
STYLE
SUB
SUP
TABLE
TBODY
TD
TEXTAREA
TFOOT
TH
THEAD
TITLE
TR
TT
U
UL
VAR
));
my($_SUBMIT_CHAR) = '*';
my($_LM) = b_use('Biz.ListModel');
my($_DYNAMIC_QUERY_KEYS) = [
b_use('Biz.FormModel')->FORM_CONTEXT_QUERY_KEY,
map(
b_use('SQL.ListQuery')->to_char($_),
qw(order_by search),
),
];
sub view_autoload {
my($proto, $method, $args, $simple_method, $suffix) = @_;
return shift->SUPER::view_autoload(@_)
unless $simple_method =~ /^($_HTML_TAGS)$/os;
my($w) = Tag(lc($simple_method), @$args ? @$args : ('', {tag_if_empty => 1}));
$w->put_unless_exists(class => $suffix)
if $suffix;
return $w->b_widget_label($method);
}
sub vs_acknowledgement {
my($proto, $die_if_not_found) = @_;
return $proto->vs_call(
'If',
[sub {$_AA->extract_label(shift->get_request)}],
$proto->vs_call(
'Tag',
'p',
[sub {
my($req) = shift->get_request;
return __PACKAGE__->vs_call(
'String',
__PACKAGE__->vs_call(
'Prose',
b_use('FacadeComponent.Text')->get_value(
'acknowledgement',
$req->get_nested(
'Action.Acknowledgement', 'label'),
$req,
),
),
);
}],
'ack',
),
);
}
sub vs_actions_column {
my($self, $actions) = @_;
return {
column_heading => 'actions',
column_widget => ListActions($actions),
};
}
sub vs_alphabetical_chooser {
my($self, $list_model) = @_;
$list_model = "Model.$list_model";
my($all) = $self->use($list_model)->LOAD_ALL_SEARCH_STRING;
return Tag(div => Join([
map(
Link(
String($_),
URI({
query => [
$list_model, '->format_query',
'ANY_LIST', {search => $_ eq 'All' ? $all : $_},
],
}),
[sub {
my(undef, $a, $search) = @_;
$a = lc($a);
return join(' ',
$a eq lc($all) ? 'all' : (),
$a eq 'a' ? () : 'want_sep',
lc($search || $all) eq $a ? 'selected' : (),
);
},
$_,
[[$list_model, '->get_query'], 'search'],
],
),
'A'..'Z', 'All',
),
]), 'alphabetical_chooser');
}
sub vs_canonical_uri_for_this_page {
return URI({
require_absolute => 1,
query => [
sub {
my($source) = @_;
my($query) = $source->ureq('query');
if (ref($query) eq 'HASH') {
foreach my $key (@$_DYNAMIC_QUERY_KEYS) {
delete($query->{$key});
}
}
return $query;
},
],
});
}
sub vs_descriptive_field {
my($proto, $field) = @_;
my($name, $attrs) = ref($field) eq 'HASH'
? ($field->{field}, $field)
: ref($field) ? @$field : $field;
$attrs ||= {};
$name =~ /^(\w+)\.(.+)/;
my($label, $input) = !$attrs->{wf_class}
&& ($attrs->{wf_type} ||
Bivio::Biz::Model->get_instance($1)->get_field_type($2))
->isa('Bivio::Type::Boolean') ? (
undef,
FormField($name, $attrs),
) : $proto->vs_form_field($name, $attrs);
$label = undef
if $attrs->{vs_descriptive_field_no_label};
return [
$label
? ($label->put(cell_class => 'label label_ok'))
: vs_blank_cell(),
Join([
$input,
$attrs->{vs_descriptive_field_no_description}
? ()
: $proto->vs_field_description($name),
], {
cell_class => 'field',
#TODO: Should this be on label, since it is the first cell in the row?
map(($_ => $attrs->{$_}), grep(/^row_\w+$/, keys(%$attrs))),
}),
];
}
sub vs_empty_list_prose {
my($self, $model) = @_;
return DIV_empty_list(Prose(vs_text("$model.empty_list_prose")));
}
sub vs_form_error_title {
my($proto, $form) = @_;
return Join([
DIV_err_title(String(vs_text('form_error_title')), {
control => [['->req'], "Model.$form", '->in_error'],
}),
DIV_err_title(String(vs_text('form_stale_data_title')), {
control => [['->req'], "Model.$form", '->has_stale_data'],
}),
]);
}
sub vs_can_group_bulletin_form {
return [sub {
my($req) = shift->req;
return $req->can_user_execute_task('GROUP_BULLETIN_FORM')
&& $req->can_user_execute_task('FORUM_MAIL_FORM');
}];
}
sub vs_field_description {
my(undef, $field_name) = @_;
return [sub {
my($source, $fn) = @_;
return ''
unless my $v = $source->req('Bivio::UI::Facade', 'Text')
->unsafe_get_value($fn, 'desc');
return DIV_desc(Prose($v));
}, $field_name];
}
sub vs_file_versions_actions_column {
return ['actions', {
column_data_class => 'list_action',
column_control => [sub {
my($source) = @_;
return ! $_LM->new_anonymous({
primary_key => [
[qw(RealmFile.realm_file_id RealmFileLock.realm_file_id)],
],
other => [['RealmFile.path', [$source->req->get('path_info')]]],
})->set_ephemeral->unsafe_load_this_or_first;
}],
column_widget => ListActions([
map({
my($task) = $_;
[
vs_text_as_prose("RealmFileVersionsList.list_action.$_"),
$task,
URI({
task_id => $task,
query => {
'ListQuery.this' => ['RealmFile.realm_file_id'],
},
path_info => [qw(->req path_info)],
}),
Not(Equals([['->get_list_model'], 'revision_number'], 'current')),
];
}
'FORUM_FILE_REVERT_FORM',
),
]),
}];
}
sub vs_filter_query_form {
my($proto, $form, $extra_columns, $attrs) = @_;
return $proto->vs_inline_form(
$form ||= 'FilterQueryForm',
[
$attrs->{text} || ClearOnFocus(
vs_edit("$form.b_filter", {
size => int(b_use('Type.Line')->get_width / 2),
wf_class => 'Text',
class => 'form-control',
}),
[['->req', "Model.$form"], '->clear_on_focus_hint'],
),
@{$extra_columns || []},
ScriptOnly({
widget => Simple(''),
alt_widget => FormButton('ok_button')
->put(label => vs_text('vs_selector_form.ok_button')),
}),
],
);
}
sub vs_header_su_link {
my(undef, $normal_widget) = @_;
return DIV_logo_su(If(
['->is_substitute_user'],
Link(
RoundedBox(Join([
'Acting as User:',
BR(),
String(['auth_user', 'display_name']),
BR(),
'Click here to exit.',
])),
b_use('IO.Config')->if_version(10,
sub {
return URI({
task_id => 'SITE_ADMIN_SUBSTITUTE_USER_DONE',
realm => vs_constant('site_admin_realm_name'),
query => undef,
});
},
sub {'LOGOUT'},
),
'su',
),
$normal_widget,
));
}
sub vs_inline_form {
my($self, $model, $cols, $attrs) = @_;
return Form(
$model,
Join($cols, ' '),
{
form_method => 'get',
want_timezone => 0,
want_hidden_fields => 0,
$attrs ? %$attrs : (),
},
);
}
sub vs_label_cell {
my($self, $model_field) = @_;
return (FormField("$model_field")->get_label_and_field)[0]
->get('label')
#TODO: Encapsulate in FormFieldLabel
->put(cell_class => 'label label_ok');
}
sub vs_list {
my($proto, $model, $columns, $attrs) = @_;
return Table(
$model,
$columns,
$proto->vs_table_attrs($model, list => $attrs),
);
}
sub vs_list_form {
my($proto, $form, $fields, $table_attrs, $options) = @_;
$options = defined($options) && ref($options) eq 'HASH'
? $options
: {
list_first => $options,
};
# Elements in $fields which are hash_refs or are "in_list" appear
# as columns. Elements which are arrays or are not "in_list" appear
# as simple form entries.
my($fm) = $_M->get_instance($form);
my($lm) = $_M->get_instance($fm->get_list_class);
my($list) = [];
my($submit);
my(@form_fields) = (map(
{
my($d) = $_;
my($field) = ref($d) eq 'HASH'
? $d
: ref($d) eq 'ARRAY'
? {
field => $d->[0],
%{$d->[1] || {}},
}
: !ref($d)
? {
field => ($d =~ /^\w+\.(\w+\.\w+)$/)[0] || $d,
}
: b_die($d, ': unknown field format');
if (!$field->{field} || $field->{column_widget}) {
push(@$list, $field);
$d = undef;
}
elsif ($fm->has_fields($field->{field})) {
if ($fm->get_field_info($field->{field}, 'in_list')) {
push(@$list, $field);
$d = undef;
}
}
elsif ($lm->has_fields($field->{field})) {
push(
@$list,
{
%$field,
wf_want_display => 1,
column_widget => $_WF->create(
$lm->simple_package_name . ".$field->{field}",
{
source_is_list_model => 1,
field => $field->{field},
%$field,
},
),
},
);
$d = undef;
}
elsif ($field->{field} =~ /^\*/) {
if (@$list) {
$submit = $field->{field};
$d = undef;
}
}
$d ? $d : ();
}
@$fields,
));
my($list_form) = @$list ? Table(
$form,
[map(
{
my($x) = !ref($_) ? {field => $_} : $_;
$x->{column_class} ||= 'field';
# So checkboxes don't have labels in the fields, just hdr
$x->{label} = ''
unless exists($x->{label});
$x;
}
@$list,
)],
$proto->vs_table_attrs($form, list => $table_attrs),
) : ();
if ($options->{indent_list} && $list_form) {
$list_form = [vs_blank_cell(), $list_form];
}
return $proto->vs_simple_form(
$form,
[
$options->{list_first}
? ($list_form, @form_fields)
: (@form_fields, $list_form),
$submit ? $submit : (),
],
$options,
);
}
sub vs_paged_detail {
my(undef, $model, $list_uri_args, $detail) = @_;
my($x) = "Model.$model";
my($p) = "$model.paged_detail.";
view_put(vs_pager => Tag(div => Join([
map(
Link(
vs_text("$p$_"),
$_ eq 'list'
? (
[
$x,
'->format_uri',
@$list_uri_args,
],
$_,
) : (
[$x, '->format_uri', uc($_) . '_DETAIL'],
{
control =>
[[$x, '->get_query'], "has_$_"],
control_off_value => Tag(
span => String(
vs_text("$p$_")), "$_ off"),
class => $_,
},
),
),
qw(prev next list)
),
]), 'pager'));
return Tag(div => $detail, 'paged_detail');
}
sub vs_paged_list {
my($proto, $model, $columns, $attrs) = @_;
$proto->vs_put_pager($model, $attrs)
unless delete($attrs->{no_pager});
return (ref($columns) eq 'ARRAY' ? Table($model, $columns) : $columns)
->put(%{$proto->vs_table_attrs($model, paged_list => $attrs)});
}
sub vs_phone {
my($proto) = @_;
return $proto->vs_call(Join => [$proto->vs_text('support_phone')]);
}
sub vs_placeholder_form {
my($proto) = @_;
return shift->vs_simple_form(@_);
}
sub vs_prose {
my(undef, $prose) = @_;
return Tag(div => Prose($prose), 'prose');
}
sub vs_put_pager {
my($proto, $model, $attrs) = @_;
view_put(vs_pager => DIV_pager(Pager({
list_class => $model,
$attrs ? %$attrs : (),
})));
$proto->vs_put_seo_list_links($model);
return;
}
sub vs_put_seo_list_links {
my($self, $model) = @_;
my($value) = ref($model)
? $model
: ["Model.$model", '->get_list_model'];
view_put(
xhtml_seo_head_links => Join([
map(
If(
[[$value, '->get_query'], "has_$_"],
LINK({
REL => $_,
HREF => URI({
require_absolute => 1,
query => [
$value,
'->format_query',
uc($_) . '_LIST',
],
}),
}),
),
qw(prev next),
),
LINK({
REL => 'canonical',
HREF => vs_canonical_uri_for_this_page(),
}),
]),
);
return;
}
sub vs_rss_task_in_head {
my($self) = @_;
return EmptyTag(link => {
control => view_widget_value('xhtml_rss_task'),
html_attrs => [qw(rel type title href)],
rel => 'alternate',
type => 'application/atom+xml',
title => Prose(
vs_text(
'rsslink', 'title', view_widget_value('xhtml_rss_task')),
),
href => URI({
task_id => view_widget_value('xhtml_rss_task'),
query => undef,
}),
});
}
sub vs_simple_form {
my($proto, $form, $rows, $attrs) = @_;
$attrs ||= {};
unless (ref($attrs)) {
b_die('expected boolean')
unless $attrs =~ /^(1|0)$/;
$attrs = {no_submit => 1};
}
my($have_submit) = 0;
my($m) = Bivio::Biz::Model->get_instance($form);
unshift(@$rows, q{'prologue})
unless grep(!ref($_) && $_ eq q{'prologue}, @$rows);
splice(
@$rows,
$#$rows + (_has_submit([$rows->[$#$rows]]) ? 0 : 1),
0,
q{'epilogue},
) unless grep(!ref($_) && $_ eq q{'epilogue}, @$rows);
push(@$rows, $proto->vs_simple_form_submit)
unless $attrs->{no_submit} || _has_submit($proto, $rows);
return Form(
$form,
Join([
$proto->vs_form_error_title($form),
$proto->vs_simple_form_container([
map({
my($x);
if ($_FF->is_blesser_of($_)) {
$_->put_unless_exists(cell_class => 'field'),
$x = [
Simple('', {cell_class => 'label label_ok'}),
$_,
];
}
elsif ($_W->is_blesser_of($_)) {
$x = [$_->put_unless_exists(cell_colspan => 2)];
}
elsif ($_ =~ s/^-//) {
$x = [Prose(vs_text($form, 'separator', $_), {
cell_colspan => 2,
cell_class => 'sep',
})];
}
elsif ($_ =~ s/^\Q$_SUBMIT_CHAR//) {
$_ = 'ok_button add_rows cancel_button'
if !$_ && $_ELFM->is_blesser_of($m);
$x = [StandardSubmit({
cell_colspan => 2,
$_ ? (buttons => $_) : (),
})];
}
elsif ($_ =~ s/^'//) {
$x = [Prose(vs_text($form, 'prose', $_), {
cell_colspan => 2,
cell_class => 'form_prose',
})];
}
elsif (ref($_) eq 'ARRAY' && ref($_->[0])) {
$x = $_;
}
else {
$x = $proto->vs_descriptive_field($_);
}
$x;
} @$rows),
], $attrs),
]),
);
}
sub vs_simple_form_container {
my($self, $values, $form_attrs) = @_;
return Grid($values, {
class => 'simple',
});
}
sub vs_simple_form_submit {
my(undef, $fields) = @_;
return $_SUBMIT_CHAR . join(' ', @{$fields || []});
}
#TODO: clean up, make into widget
sub vs_smart_date {
my($self, $field) = @_;
my($value) = ref($field)
? $field
: [$field || 'RealmFile.modified_date_time'];
return SPAN_bivio_smart_date(Simple([
sub {
my($source, $dt) = @_;
return ''
if !defined($dt);
my($now) = Type_DateTime()->now;
return DateTime($value, 'FULL_MONTH_DAY_AND_YEAR')
if Type_DateTime()->get_part(
Type_DateTime()->to_local($dt), 'year')
!= Type_DateTime()->get_part(
Type_DateTime()->to_local($now), 'year');
my($dd) = Type_DateTime()->delta_days(
Type_DateTime()->set_local_end_of_day($dt),
Type_DateTime()->local_end_of_today,
);
return DateTime($value, 'MONTH_NAME_AND_DAY_NUMBER')
if $dd > 7;
return Type_DateTime()->english_day_of_week(
Type_DateTime()->to_local($dt),
) if $dd > 1;
my($ds) = Type_DateTime()->diff_seconds($now, $dt);
return _format_integer_ago(
Type_Integer()->round($ds / 60 / 60), 'hour')
if $ds > Type_DateTime()->SECONDS_IN_DAY / 24
&& $ds < Type_DateTime()->SECONDS_IN_DAY;
return _format_integer_ago(
Type_Integer()->round($ds / 60), 'minute')
if $ds > 60 && $ds < Type_DateTime()->SECONDS_IN_DAY / 24;
return Join([
'Yesterday',
DateTime($value, 'HOUR_MINUTE_AM_PM_LC'),
], ' ')
if $dd > 0;
return DateTime($value, 'HOUR_MINUTE_AM_PM_LC')
if $ds > Type_DateTime()->SECONDS_IN_DAY / 24;
return 'Just now';
},
$value,
]));
}
sub vs_table_attrs {
my($proto, $model, $class, $attrs) = @_;
return {
class => $class,
empty_list_widget => $proto->vs_empty_list_prose($model),
%{$attrs || {}},
};
}
sub vs_tree_list {
my($proto, $model, $columns, $attrs) = @_;
$columns->[0] = $proto->vs_tree_list_control($model, $columns->[0]);
return Table(
$model,
$columns,
$proto->vs_table_attrs($model, tree_list => {
%{$attrs || {}},
want_sorting => 0,
}),
);
}
sub vs_tree_list_control {
my($proto, $model, $c) = @_;
$c = ref($c) ? {field => $c->[0], %{$c->[1] || {}}} : {field => $c}
unless ref($c) eq 'HASH';
return {
%$c,
column_widget => Join([
Replicator([['->get_list_model'], 'node_level'], SPAN_sp()),
If([['->get_list_model'], 'node_uri'],
map({
my($x) = Join([
Image(vs_text(
$_M->get_instance($model)->get_list_class,
[['->get_list_model'], 'node_state', '->get_name'],
)),
Tag(span =>
($c->{column_widget}
|| $_WF->create($model . '.' . $c->{field}, $c)),
'name',
),
]);
$_ ? Link($x, [['->get_list_model'], 'node_uri']) : $x;
} 1, 0),
),
$c->{tree_list_control_suffix_widget}
? (vs_blank_cell(2), $c->{tree_list_control_suffix_widget})
: (),
]),
column_data_class => 'node',
};
}
sub vs_trimmed_text_column {
my(undef, $field, $attr) = @_;
my($id) = $field =~ /\.(\w+)/;
$id ||= $field;
return [$field, {
column_widget => TrimmedText(String([$field]), {
ID => Join([
$id,
['->get_cursor'],
]),
}),
column_data_class => 'small_boxed_text',
$attr ? %$attr : (),
}];
}
sub vs_tuple_use_list_as_task_menu_list {
my(undef, $req) = @_;
return @{
# TupleUseList could be loaded with this so iterate, and
# this doesn't modify $req's value of Model.TupleUseList
Bivio::Biz::Model->new($req, 'TupleUseList')->map_iterate(
sub {
my($it) = @_;
return {
task_id => 'FORUM_TUPLE_LIST',
label => String($it->get('TupleUse.label')),
query => {
'ListQuery.parent_id' => $it->get(
'TupleUse.tuple_def_id'),
},
};
}
),
};
}
sub vs_user_email_list {
my($proto, $model, $other_cols, $other_tools) = @_;
view_put(
$other_tools ? (xhtml_tools => Join([@$other_tools])) : (),
xhtml_body => $proto->vs_paged_list(
$model => [
[display_name => {
column_order_by => Bivio::Biz::Model->get_instance($model)
->NAME_SORT_COLUMNS,
want_sorting => 1,
wf_list_link => {
href => URI({
query => [qw(->format_query THIS_DETAIL)],
task_id => Bivio::IO::Config->if_version(10,
sub {If(
[['->req'], '->is_super_user'],
'ADM_SUBSTITUTE_USER',
'SITE_ADMIN_SUBSTITUTE_USER',
)},
sub {'ADM_SUBSTITUTE_USER'},
),
realm => vs_constant('site_admin_realm_name'),
}),
control => Or(
[['->req'], '->is_super_user'],
['->can_substitute_user'],
),
},
}],
'Email.email',
@{$other_cols || []},
]),
);
return;
}
sub vs_xhtml_title {
return Join(
[
SPAN_realm(
String([qw(auth_realm owner display_name)]),
{
control => vs_realm_type('forum'),
},
),
vs_text_as_prose('xhtml_title'),
],
{join_separator => ' '},
);
}
sub _format_integer_ago {
my($int, $unit) = @_;
return join(' ', $int, $int == 1 ? $unit : $unit . 's', 'ago');
}
sub _has_submit {
my($proto, $rows) = @_;
return grep(
ref($_) ? Bivio::UNIVERSAL->is_blesser_of($_)
? $_->simple_package_name eq 'StandardSubmit'
: ref($_) eq 'ARRAY' && _has_submit($proto, $_)
: $_ =~ /^\*/, @$rows) ? 1 : 0;
}
1;