");
# out(qq{");
#}
sub calendar_status {
out(qq{});
my $firstname = $id;
if ($teacher_names->{$id}) {
$firstname = $teacher_names->{$id};
$firstname =~ s/ .*//;
}
out("$firstname,");
space(1);
if ($mode_admin) {
out(qq{you have admin powers.});
} elsif ($mode_static) {
out(qq{this is the timetable.});
} else {
my $plural = $n_bookings == 0 ? "no bookings" : $n_bookings == 1 ? "one booking" : "$N{$n_bookings} bookings";
# my $bookings_link = $n_bookings ? a({href=>"#my"}, $plural) : $plural;
my $bookings_left = $n_bookings_left == 0 ? "none" : $N{$n_bookings_left};
if ($too_many_bookings) {
out("you have requested $plural.");
} else {
# out(qq{you have made $bookings_link – $bookings_left left.});
out(qq{you have made $plural in total.});
if ($this_category_is_limited) {
out(qq{ You could book $bookings_left more $category.});
}
}
}
out(qq{
});
if ($too_many_bookings) {
nl();
msg("You are limited to $N{$max_bookings} bookings in any $N{$n_weeks} week period.");
msg("Please contact $conf->{administrator} if you need a higher limit.");
my $unlimited = $conf->{unlimited}{__order};
if ($unlimited && @$unlimited) {
my $x = join ", ", @$unlimited;
msg("There is no limit for $x.");
}
}
}
sub calendar_controller {
# save button
if ($week >= 0) { out($save_button); space(3); }
but("refresh");
my $bookings_list_lbl = $mode_admin_any ? "all bookings" : "my bookings";
# but($bookings_list_lbl, undef, onClick=>qq{form_submit_open_in_new_window('width=800,height=600,resizable=1,scrollbars=1', '')});
but($bookings_list_lbl);
if ($user->{is_admin}) {
but("normal");
but("admin");
but("timetable");
}
but("back"); # better name??
but("logout"); # better name??
# jsbut("logout", "window.close()");
# don't need "back" because calendar is opening in a new window now
# out(buttons(@buttons));
# dodgy check a name dropdown :)
# if ($mode_admin_any) {
# my @options = map { "$_ - $teacher_names->{$_}" } @$teacher_ids;
# space(3);
# out(dropdown({prompt=>"check a name..."}, "checkaname", \@options));
# }
# nl();
# print qq{
}; # dodgy
}
sub date_vars {
$date = $weekday_dates[$wday];
$ymd2 = $date->ymd2; # for looking up bookings
$past = $wday < $today_wday && $week == 0 || $week < 0;
# TODO know which periods are in the past??
$today = $wday == $today_wday && $week == 0;
$holiday = is_holiday($date);
$weekday = $date->{weekday};
$weekday_short = substr($weekday, 0, 3);
$month_day = $date->month_day;
$month_day_short = $date->{d}."/".$date->{m}; # AU date
$editable_day = $mode_static || !($past || ($holiday && !$conf->{allow_to_book_holidays}));
$bgcolor = $today ? "$bgcolor_today" :
$holiday ? "$bgcolor_holiday" :
"$bgcolor_other_days";
$fgcolor = $editable_day ? "$textcolor" : "$textcolor_disabled";
$day_header_bgcolor = $today ? "$bgcolor_today" : "$header_color";
}
sub resource_vars {
$resource_full = "$category : $resource";
$resource_info = $conf->{info}{$category}{$resource} || $conf->{info}{$category}{"*"}; # XXXXXX
# for looking up bookings data
}
sub period_vars {
$class = $period_n >= 0 && $period =~ /Period/;
$cellwidth = $class ? $cellwidth_major : $cellwidth_minor;
}
sub booking_vars {
$static_booker = $static_x->{$weekday}{$period}{$resource_full} || "";
$cancelled_by = undef;
if ($mode_static) {
$booker = $static_booker;
$day_name = $weekday;
} else {
$booker = $booked_x->{$ymd2}{$period}{$resource_full} || "";
$cancelled_by = $cancelled_x->{$ymd2}{$period}{$resource_full} || "";
$booker = $booker || (!$cancelled_by && $static_booker) || "";
$day_name = $ymd2;
}
$is_static = $booker && $booker eq $static_booker;
$mine = $booker && $booker eq $id;
$cancellable = $editable_day && ($mine || $booker ne "" && $mode_admin_any);
$available = $editable_day && !$booker;
$my_cancelled = $available && $cancelled_by && $static_booker eq $id;
$info_editable = $editable_day
&& !($mode_normal && $my_cancelled)
&&
($mine && !$is_static
|| $booker eq ""
|| $mode_admin && !$is_static && $booker
|| $mode_static && $is_static && $static_booker);
$cell_name = "$ymd2 : $period : $resource_full";
$cell_name =~ s/[: -.]+//g; # dodgy shit due to msie js borkedness
if ($reset) {
param($cell_name, $booker);
}
@info = @{collect_info($is_static ? $static_info_x->{$weekday} : $info_x->{$ymd2})};
}
sub collect_info {
my ($ix) = @_;
my @o;
for my $type (@{$resource_info->{__order}}) {
my $info = $ix->{$period}{$resource_full}{$type};
if (defined $info) {
push @o, $info;
}
}
return \@o;
}
sub fix_params {
$date = $monday->dup;
for $wday (0..$max_wday) {
date_vars();
for $period (@periods) {
period_vars();
for $resource (@$resources) {
resource_vars();
booking_vars();
if (param("refresh")) { param($cell_name, $booker); } # not strictly "display"
$new_booker = param($cell_name) || "";
if ($new_booker) {
$new_booker =~ s/ .*//; # this is a dodgy hack to compensate for the admin_selector becoming a text-field
param($cell_name, $new_booker);
}
}
}
}
}
sub save_changes {
# scan current week for changes to checkboxes
assume_nothing_changed();
$date = $monday->dup;
for $wday (0..$max_wday) {
date_vars();
if ($editable_day) {
for $period (@periods) {
period_vars();
for $resource (@$resources) {
resource_vars();
booking_vars();
save_changes_cell();
}
}
}
}
indexes_to_tables();
#
check_bookings($booked);
if ($too_many_bookings) {
load_booked();
}
my $made_changes = ($changed_booked || $changed_cancelled || $changed_static || $changed_info || $changed_static_info);
write_files_if_necessary();
unless ($made_changes) {
msg("No changes were made.");
nl();
}
}
sub save_changes_cell {
$new_booker = param($cell_name) || "";
my $old_booker = param("old$cell_name") || "";
my $changed_behind_scenes = $booker ne $old_booker;
my $info_missing = 0;
my $info_change_failed_because_changed_behind_scenes = 0;
if ($info_editable && $new_booker ne "" && $new_booker ne $static_booker) {
if (!$changed_behind_scenes) { # make sure the booker hasn't been changed behind the scenes!
$info_missing = update_info_if_changed($new_booker eq "");
} else {
$info_change_failed_because_changed_behind_scenes = 1;
}
}
my $failed = 0;
if ($new_booker ne $old_booker && !$info_missing) {
scroll_to_this_day_maybe();
# d($new_booker, $old_booker, $booker);
if ($changed_behind_scenes) {
# it has been changed already!
$failed = 1;
} else {
if ($cancellable) {
cancel_booking();
$available = 1;
} elsif ($booker) {
$failed = 1;
}
if ($available && $new_booker ne "") {
make_booking();
} elsif ($new_booker ne "") {
$failed = 1;
}
clear_cancelled_if_appropriate();
}
if ($failed) {
$old_booker ||= "unbooked";
$new_booker ||= "unbooked";
my $booker_msg = $booker ? "booked by $booker" : "unbooked";
my $which = $mode_static ? "timetable" : "booking";
redmsg("Could not change $which $day_name - $period - $resource : from $old_booker to $new_booker. It's already $booker_msg.");
}
}
if (!$failed && $info_change_failed_because_changed_behind_scenes) {
my $booker_msg = $booker ? "booked by $booker" : "unbooked";
redmsg("Could not change info for booking $day_name - $period - $resource. It's now $booker_msg.");
}
if ($failed or $new_booker eq $old_booker) {
# the change failed, or else the user did not change the booker;
# so make sure the checkbox or whatever reflects the real state
# which might have been changed by someone else,
# not a previous state that wasn't deliberately changed
# BLOODY HELL I so need a decent system for doing this!!
param($cell_name, $booker);
}
}
sub scroll_to_this_day_maybe {
if (!defined $scroll_to_wday) {
$scroll_to_wday = $wday;
}
}
sub indexes_to_tables {
$booked = index_to_table($booked_x);
$cancelled = index_to_table($cancelled_x);
$static = index_to_table($static_x);
$info = index_to_table($info_x);
$static_info = index_to_table($static_info_x);
}
sub write_files_if_necessary {
# write the files if necessary
if ($changed_booked) {
write_tsv_and_backup($booked_file, $booked);
$changed_booked = 0;
}
if ($changed_cancelled) {
lc_ids_last_field($cancelled);
write_tsv_and_backup($cancelled_file, $cancelled);
$changed_cancelled = 0;
}
if ($changed_static) {
lc_ids_last_field($static);
write_tsv_and_backup($static_file, $static);
$changed_static = 0;
}
if ($changed_info) {
write_tsv_and_backup($info_file, $info);
$changed_info = 0;
}
if ($changed_static_info) {
write_tsv_and_backup($static_info_file, $static_info);
$changed_static_info = 0;
}
if ($changed_holidays) {
write_tsv_and_backup($holidays_file, $holidays);
$changed_holidays = 0;
}
}
sub check_bookings {
my ($rows) = @_;
$n_bookings = 0;
$n_bookings_unlimited = 0;
my @bookings = ();
for (@$rows) {
my ($ymd, $period, $resource_full, $who) = @$_;
local ($category, $resource) = $resource_full =~ /(.*) : (.*)/;
resource_vars();
$category || die "bad full resource name: $resource_full";
if ($who eq $id || $mode_admin_any) {
my $date = Date->new_ymd(split /\./, $ymd);
my $week = Div($date->jday() - $monday1->jday(), 7);
if ($week >= 0 || $page eq "bookings" && param("show_past")) { # FIXME this is way too entangled!
if ($who eq $id) {
if (unlimited_category($category)) {
++$n_bookings_unlimited;
}
++$n_bookings;
}
$who = undef unless $mode_admin_any;
# info....
my $ymd2 = $date->ymd2;
my $ix = $info_x->{$ymd2};
my $info = $ix->{$period}{$resource_full};
my $info_values = [];
my $info_types = $resource_info ? $resource_info->{__order} : [];
for (@$info_types) { push @$info_values, $info->{$_} }
push @bookings, [$date, $week, $ymd, $period, $category, $resource_full, $who, $info_types, $info_values]
}
}
}
$n_bookings_left = $max_bookings - ($n_bookings - $n_bookings_unlimited);
$too_many_bookings = !$mode_admin_any && $n_bookings_left < 0;
unless ($too_many_bookings) { $my_bookings = \@bookings; }
}
sub write_tsv_and_backup {
my ($filename, $rows) = @_;
backup($filename);
write_tsv($filename, $rows);
}
sub pretty_print_booking {
my ($date, $week, $ymd, $period, $category, $resource_full, $who, $info_types, $info_values) = @_;
if (defined $who && exists $teacher_names->{$who}) {
$who = "$who - $teacher_names->{$who}";
}
my $resource_url = "?" . url_encode(week=>$week, category=>$category);
my $resource_display = $resource_full;
$resource_display =~ s/: \.$//;
my $resource_link = a({href=>$resource_url}, nobrify($resource_display));
my $row = nobrify(week_desc($week))."\t".nobrify($date->wmd_short)."\t".nobrify($period)."\t".$resource_link.(defined $who ? "\t".nobrify($who) : "");
if (@$info_values) {
$row .= "\t";
for (@$info_values) { $row .= "$_ "; }
}
return $row;
}
sub make_booking {
if ($new_booker eq $static_booker) {
clear_cancelled();
} else {
if ($mode_static) {
$static_x->{$weekday}{$period}{$resource_full} = $new_booker;
$changed_static = 1;
if (!$new_booker) {
clear_cancelled();
}
} else {
$booked_x->{$ymd2}{$period}{$resource_full} = $new_booker;
$changed_booked = 1;
}
}
booking_vars();
}
sub update_info_if_changed {
my ($allow_blank) = @_;
my $info_missing = 0;
my $ix = ($mode_static ? $static_info_x->{$weekday} : $info_x->{$ymd2}) ||= {};
$info_missing_msg = 0;
for my $type (@{$resource_info->{__order}}) {
my $new_info = param("$cell_name : $type") || "";
if ($new_info eq "$type..." || $new_info eq $type) { $new_info = ""; }
if ($new_info eq "" && !$allow_blank) {
if ($info_missing_msg == 0) {
my $which = $mode_static ? "timetable" : "booking";
redmsg("$which $day_name - $period - $resource : please enter the extra info for this booking");
out(br());
}
$info_missing_msg ||= 1;
$info_missing = 1;
}
}
if ($info_missing) {
scroll_to_this_day_maybe();
return 1; # info_missing
}
for my $type (@{$resource_info->{__order}}) {
my $old_info = $ix->{$period}{$resource_full}{$type};
my $new_info = param("$cell_name : $type") || "";
trim($new_info);
param("$cell_name : $type", $new_info);
if ($new_info ne $old_info) {
scroll_to_this_day_maybe();
if (exists $conf->{types}{$type}) {
if (! exists $conf->{types}{$type}{$new_info}) {
die qq{invalid value "$new_info" given for $type};
}
}
$ix->{$period}{$resource_full}{$type} = $new_info;
if ($mode_static) { $changed_static_info = 1; }
else { $changed_info = 1; }
}
}
return 0;
}
sub cancel_booking {
if ($is_static) {
if ($mode_static) {
delete $static_x->{$weekday}{$period}{$resource_full};
$changed_static = 1;
} else {
$cancelled_x->{$ymd2}{$period}{$resource_full} = $id;
$changed_cancelled = 1;
}
} else {
delete $booked_x->{$ymd2}{$period}{$resource_full};
$changed_booked = 1;
}
my $ix = $mode_static ? $static_info_x->{$weekday} : $info_x->{$ymd2};
delete $ix->{$period}{$resource_full};
if ($mode_static) { $changed_static_info = 1; }
else { $changed_info = 1; }
booking_vars();
}
sub clear_cancelled_if_appropriate {
if ($cancelled_by && !$static_booker) {
clear_cancelled();
}
}
sub clear_cancelled {
delete $cancelled_x->{$ymd2}{$period}{$resource_full};
$changed_cancelled = 1;
booking_vars();
}
sub mode_vars {
if (!sd("mode")) { sd_set("mode", "normal"); }
$mode_normal = sd("mode") eq "normal";
$mode_admin = sd("mode") eq "admin";
$mode_static = sd("mode") eq "static";
$mode_admin_any = $mode_admin || $mode_static;
}
sub which_page_are_we_on {
$page = param("page");
$previous_page = $page;
dflt($previous_page, "");
$page ||= "main";
# d($page);
# TODO a nicer way for this,
# like a hash generated by a button link function
# page, button, action, goto ; on leave, on arrive, ...
# one day I'll think about this properly!
if ($page eq "main" && param("edit resources")) {
$page = "resources";
} elsif ($page eq "main" && param("edit holidays")) {
$page = "holidays";
} elsif ($page eq "calendar" && param("back")) {
$page = "main";
} elsif ($page eq "calendar" && param("my bookings") || param("all bookings")) {
$page = "bookings";
} elsif ($page eq "bookings" && param("back")) {
$page = "calendar";
} elsif ($page eq "resources" && param("back")) {
$page = "main";
} elsif ($page eq "holidays" && param("back")) {
$page = "main";
} elsif ($page eq "main" && param("category")) {
$page = "calendar";
}
# d($page);
}
sub display_cell {
my $bgcolor = $bgcolor; # white
if ($mine) {
$bgcolor = $static_booker ? $color_mine_static : $color_mine_booked;
} elsif ($booker) {
$bgcolor = $static_booker && !$cancelled_by ? $color_other_static : $color_other_booked;
} elsif (!$booker && $cancelled_by) {
$bgcolor = $static_booker eq $id ? $color_mine_cancelled : $color_other_cancelled;
}
my $cw = $class ? $cellwidth_major : $cellwidth_minor;
out(qq{}) if !$class;
out(qq{});
my $left_w = $cw * .5;
my $right_w = $cw * .5;
# out(qq{});
# out(qq{});
selector();
# out(qq{ | });
# out(qq{ });
# if (!$singleton) { # multiple resources, so display the resource name and the booker
# } else { # a single resource - we just display the booker
# out(qq{ | });
# }
if ($resource_info) {
out(qq{ });
# out(qq{ }); # FIXME this is ultra dodgy, not sure the right way :/
info_selector();
}
out(" | ");
out(qq{}) if !$class;
$cell_count++;
}
sub calendar_legend {
my @colors1 = qw(
key: white
booked color_other_booked
timetable color_other_static
);
# cancelled color_other_cancelled
# today bgcolor_today
my @colors2 = qw(
unbooked white
your_booking color_mine_booked
your_timetable color_mine_static
);
# your_cancelled color_mine_cancelled
# holiday bgcolor_holiday
out(qq{});
for (\@colors1, \@colors2) {
out("");
while (@$_) {
my ($name, $var) = splice @$_, 0, 2;
$name =~ tr/_/ /;
no strict 'refs';
my $color = $$var;
out(qq{ $name | }); # bogus when lines split
}
out("
");
}
out(qq{
});
}
# TODO get hardcoded ou=foo ... out of lookup_staff_etc
#sub allowed_domains {
# for (
#}
#
#sub tree_to_domains {
# my ($tree) = @_;
# for (keys %$tree) {
#
# }
#}
sub lookup_staff_etc {
my %long_uid_of;
my %names;
my @short_uids;
my $dup_uids;
# die Dumper($ou_allowed);
# XXX TODO get hardcoded ou=foo ... out of lookup_staff_etc
for my $domain ('ou=staff', 'ou=administrators') {
my $users = ldap_lookup_users_uid_name($domain);
if (ref $users) {
USER: for my $user (@$users) {
my $long_uid = $user->{uid};
my $short_uid = uc_or_lc_id($user->{short_uid});
if (!defined $short_uid or $short_uid eq "") {
# msg("no short_uid ($short_uid_type) for $long_uid");
next USER;
}
if (exists $long_uid_of{$short_uid}) {
msg("duplicate short_uid `$short_uid' ($short_uid_type): for $long_uid_of{$short_uid}, $long_uid");
$dup_uids = 1;
} else {
$long_uid_of{$short_uid} = $long_uid;
}
push @short_uids, $short_uid; # for dropdown
my ($name1, $name2) = ldap_get_name($user);
$name2 = uc($name2) if $conf->{surname_uppercase};
my $fullname = "";
if ($name1 and $name2) { $fullname = "$name1 $name2"; }
$names{$short_uid} = $fullname;
}
} else {
die "bad: ldap_lookup in domain $domain returned $users";
}
}
if ($dup_uids) {
die "duplicate short_uids - please contact $conf->{administrator}";
}
@short_uids = sort @short_uids;
return (\%names, \@short_uids);
}
sub calculate_weekday_dates {
$date = $monday->dup;
@weekday_dates = ();
for $wday (0..$max_wday+1) { # +1 is needed for a hack :)
push @weekday_dates, $date->dup;
$date->tomorrow();
}
}
# table subs - todo OO / put in library ?
my ($max_x, $max_y) = (0, 0);
sub cell {
my ($func_ref, @args) = @_;
$max_x = $x if $x > $max_x;
$max_y = $y if $y > $max_y;
if ($x < 0 || $y < 0) { return; } # ???
redirect_out(\($table->[$x][$y]), $func_ref, @args);
}
sub flip_table {
$table = flip_2d($table);
my $rowspan_ = flip_2d($rowspan);
my $colspan_ = flip_2d($colspan);
$colspan = $rowspan_;
$rowspan = $colspan_;
my $tmp = $max_x;
$max_x = $max_y;
$max_y = $tmp;
}
sub flip_2d {
my ($old_table) = @_;
my $new_table;
for $y (0..$max_y) {
for $x (0..$max_x) {
$new_table->[$y][$x] = $old_table->[$x][$y];
}
}
return $new_table;
}
sub table_new {
$table = [];
$rowspan = [];
$colspan = [];
}
#sub table_hide_top_header {
# $y = 0;
# for $x (0..$max_x) {
# my $v = $table->[$x][$y];
# if ($v) {
# $table->[$x][$y] =~ s/<(td|th) /<$1 style="visibility: hidden" /;
# }
# }
# $table->[0][1] =~ s{(<(td|th).*?>|)}{$1}
# or die "can't hide 2nd table header";
#}
sub display_table {
my ($n_rows_in_header, $extra_blank_row_at_bottom) = @_;
# d($colwidth);
if ($n_rows_in_header) { Tag("thead"); }
for $y (0..$max_y+$extra_blank_row_at_bottom) {
if ($n_rows_in_header && $y == $n_rows_in_header) { Ctag("thead"); Tag("tbody"); }
print "";
for $x (0..$max_x) {
my $cell = $table->[$x][$y];
my $cs = $colspan->[$x][$y];
my $rs = $rowspan->[$x][$y];
my $width = delete $colwidth->[$x];
# this stuff is dodgy but works, should simplify it...
if ($cs) {
$cell =~ s/<(td|th) /<$1 colspan="$cs" /;
for my $x1 (1..$cs-1) {
defined $table->[$x+$x1][$y] and die "colspan covers data";
$table->[$x+$x1][$y] = '';
}
}
if ($width && (!$n_rows_in_header || $y >= $n_rows_in_header)) { # XXX
$cell =~ s/<(td|th) /<$1 width="$width" /;
}
if ($rs) {
$cell =~ s/<(td|th) /<$1 rowspan="$rs" /;
for my $y1 (1..$rs-1) {
defined $table->[$x][$y+$y1] and die "colspan covers data";
$table->[$x][$y+$y1] = '';
}
}
if ($cs and $rs) {
for my $x1 (1..$cs-1) {
for my $y1 (1..$rs-1) {
defined $table->[$x+$x1][$y+$y1] and die "colspan covers data";
$table->[$x+$x1][$y+$y1] = '';
}
}
}
out(defined $cell ? $cell : $sep);
}
print "
";
}
if ($n_rows_in_header) { Ctag("tbody"); }
}
sub blank_cell {
out(qq{});
out(qq{ | });
}
#sub display_week_selector {
# out(qq{});
# week_selector();
# out(qq{ | });
#}
# TODO separate this into a different script maybe
sub page_front {
hdr(title => "Booking Resources", bg_color => $body_bgcolor, fg_color => $body_textcolor, link_color => $body_linkcolor);
if ($user->{is_admin}) {
if (param("erase timetable")) {
erase_the_timetable();
redmsg("The timetable has been erased!");
msg("If you did that by mistake, it can be recovered from a backup, but not automatically.");
nl();
}
}
my $b = buttons("logout");
out($b);
if ($user->{is_admin}) {
space(6);
my @buttons = ("edit resources", "edit holidays");
out(buttons(@buttons));
space(6);
but("erase timetable", "erase timetable", jsconfirm2("Are you SURE you want to erase the timetable? You should only do this at the start of a new term...", "Are you REALLY SURE you want to erase the timetable? Don't do this lightly!!!"));
}
nl(); nl();
category_chooser();
}
sub category_chooser {
# my @clean_window_onclick = (onClick=>"window.open(this.href,'calendar','width=800,height=600,resizable=1'); return false;");
my $categories = non_empty_categories();
out("What type of resource?"); nl();
my $tbl = "";
for my $category (@$categories) {
my $url = "?" . url_encode(category=>$category);
# my @clean_window_onclick = (onClick=>"window.open(this.href,'calendar','width=800,height=600,resizable=1'); return false;");
# my $link = a({href=>$url, @clean_window_onclick}, $category);
my $link = a({href=>$url}, $category);
$tbl .= "\t" . $link;
for my $week (1, 2) {
my $week_desc = week_desc($week);
my $url = "?" . url_encode(category=>$category, week=>$week);
# my $link = (" "x3) . a({href=>$url, @clean_window_onclick}, $week_desc);
my $link = (" "x3) . a({href=>$url}, $week_desc);
$tbl .= "\t$link";
}
$tbl .= "\n";
}
$tbl = tbl_tsv($tbl);
$tbl =~ s{};
out($tbl);
}
sub non_empty_categories {
my @non_empty_categories = grep {
my $resources = $conf->{resources}{$_};
$resources && ref $resources && @{$resources->{__order}};
} @{$conf->{resources}{__order}};
return \@non_empty_categories;
}
sub page_bookings {
calendar_init(); # factor this into bits??
# we need load_calendar_data() for sure
param("sortby", param("sortby")||"date");
param("sortby2", param("sortby2")||"resource");
param("show_past", param("show_past")||0);
param("show_all", param("show_all")||0);
$page_bookings_show_all = param("show_all") || !$mode_admin_any;
@$my_bookings = grep {
my ($date, $week, $ymd, $period, $cat, $resource_full, $who, $info_types, $info_values) = @$_;
$page_bookings_show_all || $category eq $cat;
} @$my_bookings;
@$my_bookings = sort { cmp_bookings($a, $b) } @$my_bookings;
if (param("tsv") || param("excel")) {
dump_as_tsv_or_excel();
return;
}
my $title;
if ($mode_admin_any) {
if (param("show_all")) {
$title = "All bookings (all types of resource)";
} else {
$title = "All $category bookings";
}
} else {
$title = "All of your Bookings";
}
hdr(title => $title);
out(hidden("category")); # FIXME these really should be done in a "page" state or using sd_state maybe...
out(hidden("week"));
my @buttons; # other buttons, save is done differently now
push @buttons, "back"; # better name??
push @buttons, "logout";
out(buttons(@buttons));
if ($mode_admin_any) {
space(20);
out(page_bookings_show_hide_past_link());
space(4);
out(page_bookings_show_hide_all_bookings());
space(4);
out(page_bookings_excel_link());
space(4);
out(page_bookings_tsv_link());
}
nl(); nl();
# out(a({name=>"my"}, h2($title.(" "x10).q{top})));
# d(@$my_bookings);
if (@$my_bookings == 0) {
msg("You have not made any bookings!");
return;
}
# create the bookings table
my $tbl = page_bookings_sort_by_link("date")."\t\t\t".page_bookings_sort_by_link("resource");
if ($mode_admin_any) {
$tbl .= "\t".page_bookings_sort_by_link("person");
}
$tbl .= "\t"."reason";
$tbl .= "\n";
for (@$my_bookings) {
$tbl .= pretty_print_booking(@$_) . "\n";
}
$tbl = tbl_tsv($tbl);
## $tbl =~ s/};
$tbl =~ s/),$1 | | ,g;
out($tbl);
}
sub cmp_bookings {
my ($x, $y) = @_;
my $rv = 0;
for my $type (param("sortby"), param("sortby2")) {
$rv ||= cmp_bookings_1($x, $y, $type);
}
return $rv;
}
sub cmp_bookings_1 {
my ($x, $y, $type) = @_;
my $sortby = $type;
my $reverse_sort = 0;
if ($sortby =~ s/^(-)//) { $reverse_sort = 1; }
my $rv = 0;
if ($sortby eq "date") {
$rv = cmp_bookings_by_date($x, $y);
} elsif ($sortby eq "resource") {
$rv = cmp_bookings_by_resource($x, $y);
} elsif ($sortby eq "person") {
$rv = cmp_bookings_by_person($x, $y);
}
$rv = -$rv if $reverse_sort;
return $rv;
}
sub cmp_bookings_by_date {
my ($x, $y) = @_;
my ($date_x, $week_x, $ymd_x, $period_x, $category_x, $resource_x, $who_x) = @$x;
my ($date_y, $week_y, $ymd_y, $period_y, $category_y, $resource_y, $who_y) = @$y;
return $ymd_x cmp $ymd_y; # || $period_x cmp $period_y;
}
sub cmp_bookings_by_resource {
my ($x, $y) = @_;
my ($date_x, $week_x, $ymd_x, $period_x, $category_x, $resource_x, $who_x) = @$x;
my ($date_y, $week_y, $ymd_y, $period_y, $category_y, $resource_y, $who_y) = @$y;
return $category_x cmp $category_y || $resource_x cmp $resource_y;
}
sub cmp_bookings_by_person {
my ($x, $y) = @_;
my ($date_x, $week_x, $ymd_x, $period_x, $category_x, $resource_x, $who_x) = @$x;
my ($date_y, $week_y, $ymd_y, $period_y, $category_y, $resource_y, $who_y) = @$y;
return $who_x cmp $who_y;
}
sub page_bookings_sort_by_link {
my ($what) = @_;
my $sortby = $what;
my $backwards = "";
my $marker = "";
my $sortby2 = param("sortby2");
my $show_past = param("show_past");
my $show_all = param("show_all");
if ($sortby eq param("sortby")) {
$backwards = " backwards"; $sortby = "-$sortby";
# ??? disable reverse sort?
$marker = ' \\/';
} elsif ("-$sortby" eq param("sortby")) {
$marker = ' /\\';
} else {
if ($sortby eq param("sortby2")) {
$marker = " v";
} elsif ("-$sortby" eq param("sortby2")) {
$marker = " ^";
}
# ??? disable secondary sort markers?
$sortby2 = param("sortby");
}
return qq{$what$marker};
}
sub page_bookings_show_hide_past_link {
# FIXME this is getting ultra bogus, but I don't have time to refactor it right now
my $sortby = param("sortby");
my $sortby2 = param("sortby2");
my $show_past = ! param("show_past");
my $show_all = param("show_all");
my $show_hide = $show_past ? "show" : "hide";
return qq{$show_hide the past};
}
sub page_bookings_show_hide_all_bookings {
# FIXME this is getting ultra bogus, but I don't have time to refactor it right now
my $sortby = param("sortby");
my $sortby2 = param("sortby2");
my $show_past = param("show_past");
my $show_all = ! param("show_all");
my $show_hide = $show_all ? "show ALL bookings" : "show only $category";
return qq{$show_hide};
}
sub page_bookings_tsv_link {
# FIXME this is getting ultra bogus, but I don't have time to refactor it right now
my $sortby = param("sortby");
my $sortby2 = param("sortby2");
my $show_past = param("show_past");
my $show_all = param("show_all");
return qq{tab-delimited text};
}
sub page_bookings_excel_link {
# FIXME this is getting ultra bogus, but I don't have time to refactor it right now
my $sortby = param("sortby");
my $sortby2 = param("sortby2");
my $show_past = param("show_past");
my $show_all = param("show_all");
return qq{excel spreadsheet};
}
# ---------------------------------------------------------------------
sub page_holidays_editor {
hdr(title => "Holidays Editor", bg_color => $body_bgcolor, fg_color => $body_textcolor, link_color => $body_linkcolor);
load_holidays();
sort_holidays();
$today = $today_date;
my $b = buttons("save", "refresh", "back", "logout");
out($b); nl(); nl();
if ($previous_page eq "holidays" && !param("refresh")) { save_holidays(); }
Tag("table", id=>"scrolltable", cellspacing=>3, cellpadding=>0, border=>0);
Tag("thead");
Ctag("thead");
out("From date | To date | Name of holiday |
\n");
out("");
tf("from_date new", "from-date", size=>10, select_on_focus());
out(" | ");
tf("to_date new", "to-date", size=>10, select_on_focus());
out(" | ");
tf("name new", "name of holiday", size=>25, select_on_focus());
out(" | ");
space(2);
but("add");
out(" |
");
Tag("tbody");
for my $h (@$holidays) {
my ($from_date, $to_date, $name) = @$h;
my ($from_date_display) = display_date($from_date);
my ($to_date_display) = display_date($to_date);
if ($to_date eq $from_date) { $to_date_display = ""; }
my $key = "$from_date $to_date";
out("");
tf("from_date $from_date $to_date", $from_date_display, size=>10);
out(" | ");
tf("to_date $from_date $to_date", $to_date_display, size=>10);
out(" | ");
tf("name $from_date $to_date", $name, size=>25);
out(" | ");
space(2);
but("del $from_date $to_date", "delete", jsconfirm("Are you sure you want to delete this holiday?"));
out(" |
");
}
out("
");
Ctag("tbody");
Ctag("table");
}
sub page_resource_editor {
load_calendar_data(); # factor this into bits??
hdr(title => "Resource Editor", bg_color => $body_bgcolor, fg_color => $body_textcolor, link_color => $body_linkcolor);
my $buttons = buttons("save", "refresh", "back", "logout");
if ($previous_page eq "resources" && !param("refresh")) { save_resources(); }
my $show_hide_width = 20;
my $textfields_width = 300;
my $start_row = qq{ | };
Tag("table", id=>"scrolltable");
Tag("thead");
Ctag("thead");
Tag("tbody");
Tag("tr");
Tag("td");
out($buttons);
Ctag("td");
Ctag("tr");
Tag("tr");
Tag("td");
for my $category ( @{$conf->{resources}{__order}} ) {
my $resources = $conf->{resources}{$category};
my $n_resources = n_resources($category);
Tag("table", cellspacing=>1, cellpadding=>0, border=>0);
out(" |
");
my $resources_id = "resources $category";
my $resources_visible = 0;
my @show_hide_attrs = show_hide($resources_id, $resources_visible);
out(" | ");
tf("cat $category", $category, size=>25);
out(" | ");
space(2);
if ($n_resources <= 1) {
my $what = $n_resources == 0 ? "resource-type" : "resource and its type";
but("delcat $category", "delete", jsconfirm("Are you sure you want to delete this $what?"));
}
out(" |
\n");
out("");
Tag("table", cellspacing=>1, cellpadding=>0, border=>0, @show_hide_attrs);
if (ref $resources) {
for my $resource (@{$resources->{__order}}) {
my $resource_full = "$category : $resource";
my $desc = $resources->{$resource} || "";
out($start_row);
space(3);
my $sof = 0;
my ($resource_display, $desc_display) = ($resource, $desc);
if ($resource eq "." && $desc eq "") {
$resource_display = "the only...";
$desc_display = "description...";
$sof = 1;
}
tf("res $resource_full", $resource_display, size=>7, ($sof ? select_on_focus() : ()));
tf("desc $resource_full", $desc_display, ($sof ? select_on_focus() : ()));
out("");
space(3);
but("delres $resource_full", "delete", jsconfirm("Are you sure you want to delete this resource?"));
out(" | \n");
}
}
out($start_row);
space(3);
tf("newres $category", "new...", size=>7, select_on_focus());
tf("newdesc $category", "description...", select_on_focus());
out("");
space(4);
but("add");
out(" | ");
# space(2);
# but("addres $resource", "add");
out(" | \n");
}
out("");
Tag("table", cellspacing=>1, cellpadding=>0, border=>0);
out($start_row);
tf("newcat", "new (type of) resource...", size=>25, select_on_focus());
out("");
space(4);
but("add");
out(" | ");
# space(2);
# but("add:res:$resource", "add");
out(" | \n");
out("");
Ctag("td");
Ctag("tr");
Ctag("tbody");
Ctag("table");
my $href = "/admin/?" . url_encode(filename=>$bookings_conf_file);
# out(qq{If you want to rearrange the resources list, you can edit it directly; Be very careful if you do that. You cannot rename resources or types,
or move a resource from one type to another using that editor. If you try doing that, things will break.});
}
# FIXME ?
sub n_resources {
my ($category) = @_;
my $resources = $conf->{resources}{$category};
my $n_resources = ref $resources ? 0+@{$resources->{__order}} : 0;
return $n_resources;
}
sub save_holidays {
assume_nothing_changed(); # not needed?
msgs_start();
my @new_holidays;
my %is_holiday;
for my $h (@$holidays) {
my ($from_date, $to_date, $name) = @$h;
if (param("del $from_date $to_date")) {
msg("deleting holiday: $name");
$changed_holidays = 1;
} else {
my $new_from_date = param("from_date $from_date $to_date");
my $new_to_date = param("to_date $from_date $to_date");
my $new_name = param("name $from_date $to_date");
if ($new_from_date) {
if ($new_to_date !~ /\d/) { $new_to_date = $new_from_date; }
$new_from_date = fix_date($new_from_date);
if ($new_from_date) { $new_to_date = fix_date($new_to_date, $new_from_date) };
if ($new_from_date && $new_to_date && $name
&& ($new_from_date ne $from_date ||
$new_to_date ne $to_date ||
$new_name ne $name)) {
$from_date = $new_from_date;
$to_date = $new_to_date;
$name = $new_name;
msg("changing holiday: $name");
$changed_holidays = 1;
}
push @new_holidays, [$from_date, $to_date, $name];
}
$is_holiday{"$from_date $to_date"} = $name;
}
}
# check for a new holiday
my ($from_date, $to_date, $name) = (param("from_date new"), param("to_date new"), param("name new"));
if ($to_date !~ /\d/) { $to_date = $from_date; }
if ($from_date =~ /\d/ && $to_date =~ /\d/ && $name) {
$from_date = fix_date($from_date);
if ($from_date) { $to_date = fix_date($to_date, $from_date) };
if ($from_date && $to_date) {
if ($is_holiday{"$from_date $to_date"}) {
my $desc;
if ($to_date eq $from_date) {
$desc = "on ".display_date($from_date);
} else {
$desc = "from ".display_date($from_date)." to ".display_date($to_date);
}
msg(qq{there is already a holiday $desc - $is_holiday{"$from_date $to_date"}});
} else {
msg("adding holiday: $name");
push @new_holidays, [$from_date, $to_date, $name];
$changed_holidays = 1;
}
}
}
@$holidays = @new_holidays;
sort_holidays();
write_files_if_necessary();
}
sub fix_date {
my ($date, $should_be_close_to) = @_;
my (@parts) = grep {$_ ne ""} split /\D+/, $date;
if (@parts == 3) {
if ($parts[0] > 1000) {
# YYYY.MM.DD
$date = sprintf("%04d.%02d.%02d", @parts)
} elsif ($parts[2] > 1000) {
# DD.MM.YYYY - FIXME won't work for USA! lol
$date = sprintf("%04d.%02d.%02d", reverse @parts)
} else {
# assume D.M.Y !
$date = sprintf("%04d.%02d.%02d", $parts[2]+2000, @parts[1,0])
}
} elsif (@parts == 2) {
# assume d/m, guess the year to be this year or next year if this date is in the past
my ($y, $m, $d) = ($today->{y}, $parts[1], $parts[0]);
if ($should_be_close_to) {
($y) = $should_be_close_to =~ /^(\d\d\d\d)\./
or die "bad date: $should_be_close_to";
$y ++ if sprintf("%04d.%02d.%02d", $y, $m, $d) lt $should_be_close_to;
} else {
$y ++ if $m < $today->{m} || $m == $today->{m} && $d < $today->{d};
}
$date = sprintf("%04d.%02d.%02d", $y, $m, $d);
} else {
msg("this is a strange looking date: $date - I can't understand it!");
return undef;
}
return $date;
}
sub display_date {
my ($date) = @_;
my (@parts) = grep {$_ ne ""} split /\D+/, $date;
return join " / ", map {0+$_} reverse @parts;
}
sub save_resources {
assume_nothing_changed(); # not needed?
msgs_start();
CATEGORY: for my $category ( @{$conf->{resources}{__order}} ) {
# delete category?
my $delcat = param("delcat $category");
if ($delcat) {
delete_category($category);
next CATEGORY;
}
# for each resource
my $resources = $conf->{resources}{$category};
RESOURCE: for my $resource (@{$resources->{__order}}) {
my $resource_full = "$category : $resource";
# delete resource?
my $delres = param("delres $resource_full");
if ($delres) {
delete_resource($category, $resource);
next RESOURCE;
}
# change resource description?
my $desc = $resources->{$resource} || "";
my $res_change_desc = param("desc $resource_full");
if ($desc ne $res_change_desc && name_okay($res_change_desc)) {
change_resource_description($category, $resource, $res_change_desc);
}
# rename resource?
my $res_change_name = param("res $resource_full");
if ($resource ne $res_change_name && name_okay($res_change_name)) {
rename_resource($category, $resource, $res_change_name);
}
}
# add a new resource in this category?
my $newres = param("newres $category");
my $newdesc = param("newdesc $category");
if (name_okay($newres)) {
$newdesc = "" if !name_okay($newdesc);
add_resource($category, $newres, $newdesc);
}
# rename cateogry?
my $cat_change_name = param("cat $category");
if ($category ne $cat_change_name && name_okay($cat_change_name)) {
rename_category($category, $cat_change_name);
}
}
# add a new category?
my $newcat = param("newcat");
if (name_okay($newcat)) {
add_category($newcat);
}
msgs_end();
write_files_if_necessary();
if ($changed_bookings_conf) {
write_bookings_conf_and_backup();
}
}
sub delete_category {
my ($category) = @_;
# CHECK IT IS EMPTY!
if (n_resources($category) > 1) {
die "attempt to delete multiple resources, too dangerous!";
}
if (n_resources($category) == 1) {
# there is one resource in this category, delete it
my ($resource) = @{$conf->{resources}{$category}{__order}};
delete_resource($category, $resource);
}
msg("deleting type: $category");
# there should be no data associated with this anywhere, so just delete it
tree_delete($conf->{resources}, $category);
$changed_bookings_conf = 1;
}
sub delete_resource {
my ($category, $resource) = @_;
msg("deleting resource: $category : $resource");
delete_all_bookings_and_timetable_concerning($category, $resource);
tree_delete($conf->{resources}{$category}, $resource);
$changed_bookings_conf = 1;
}
sub delete_all_bookings_and_timetable_concerning {
my ($category, $resource) = @_;
my $resource_full = "$category : $resource";
my $column = 2; # date/day period what
$changed_booked = 1 if grep_out($booked, $column, $resource_full);
$changed_static = 1 if grep_out($static, $column, $resource_full);
$changed_cancelled = 1 if grep_out($cancelled, $column, $resource_full);
$changed_info = 1 if grep_out($info, $column, $resource_full);
$changed_static_info = 1 if grep_out($static_info, $column, $resource_full);
}
sub grep_out {
my ($table, $column, $what) = @_;
my $changed = 0;
@$table = grep {
if ($_->[$column] eq $what) {
++$changed;
0
}
else { 1 }
} @$table;
return $changed;
}
# TODO maybe should have a unique short name for each resource across all categories, and allow each resource to go in multiple categories - that would be a moderately big change though, so forget it for now
sub rename_category {
my ($category, $cat_change_name) = @_;
msg("renaming resource/type: `$category' to `$cat_change_name'");
if (tree_rename($conf->{resources}, $category, $cat_change_name)) {
$changed_bookings_conf = 1;
rename_all_bookings_concerning_category($category, $cat_change_name);
} else {
msg("rename failed, does that category already exist?");
}
if ($conf->{info}) {
tree_rename($conf->{info}, $category, $cat_change_name);
}
}
sub rename_resource {
my ($category, $resource, $res_change_name) = @_;
my $resource_full = "$category : $resource";
my $res_change_name_full = "$category : $res_change_name";
msg("renaming resource: `$resource_full' to `$res_change_name_full'");
if (tree_rename($conf->{resources}{$category}, $resource, $res_change_name)) {
msg("yes, tree_rename ok");
$changed_bookings_conf = 1;
rename_all_bookings_concerning_resource($resource_full, $res_change_name_full);
} else {
msg("rename failed, does that resource already exist?");
}
if ($conf->{info}{$category}) {
tree_rename($conf->{info}{$category}, $resource, $res_change_name);
}
}
sub rename_all_bookings_concerning_category {
my ($category, $category_new) = @_;
my $column = 2; # date/day period what
$changed_booked = 1 if substitute_category($booked, $column, $category, $category_new);
$changed_static = 1 if substitute_category($static, $column, $category, $category_new);
$changed_cancelled = 1 if substitute_category($cancelled, $column, $category, $category_new);
$changed_info = 1 if substitute_category($info, $column, $category, $category_new);
$changed_static_info = 1 if substitute_category($static_info, $column, $category, $category_new);
}
sub rename_all_bookings_concerning_resource {
my ($resource, $resource_new) = @_;
my $column = 2; # date/day period what
$changed_booked = 1 if substitute_resource($booked, $column, $resource, $resource_new);
$changed_static = 1 if substitute_resource($static, $column, $resource, $resource_new);
$changed_cancelled = 1 if substitute_resource($cancelled, $column, $resource, $resource_new);
$changed_info = 1 if substitute_resource($info, $column, $resource, $resource_new);
$changed_static_info = 1 if substitute_resource($static_info, $column, $resource, $resource_new);
}
sub substitute_category {
my ($table, $column, $category, $category_new) = @_;
my $changed = 0;
$category = "$category :";
$category_new = "$category_new :";
for (@$table) {
if (starts_with($_->[$column], $category)) {
substr($_->[$column], 0, length($category), $category_new);
$changed = 1;
}
}
return $changed;
}
sub starts_with {
my ($a, $b) = @_;
return index($a, $b) == 0;
}
sub substitute_resource {
my ($table, $column, $resource, $resource_new) = @_;
my $changed = 0;
for (@$table) {
if ($_->[$column] eq $resource) {
$_->[$column] = $resource_new;
$changed = 1;
}
}
return $changed;
}
sub change_resource_description {
my ($category, $resource, $res_change_desc) = @_;
msg("setting description for `$category : $resource' to `$res_change_desc'");
$conf->{resources}{$category}{$resource} = $res_change_desc;
$changed_bookings_conf = 1;
}
sub add_resource {
my ($category, $newres, $newdesc) = @_;
if ($newres ne ".") {
msg("adding resource: `$category : $newres'");
}
$conf->{resources}{$category} ||= tree_empty();
if (tree_append($conf->{resources}{$category}, $newres, $newdesc) == 0) {
msg("Could not add resource: `$category : $newres' - already exists!");
return;
};
$changed_bookings_conf = 1;
}
sub add_category {
my ($newcat) = @_;
msg("adding resource/type: $newcat");
if (tree_append($conf->{resources}, $newcat, tree_empty()) == 0) {
msg("Could not add resource/type: `$newcat' - already exists!");
return;
}
# add a single new resource in that category with name `.'
add_resource($newcat, ".", "");
$changed_bookings_conf = 1;
}
sub assume_nothing_changed {
$changed_cancelled = 0;
$changed_booked = 0;
$changed_static = 0;
$changed_info = 0;
$changed_static_info = 0;
$changed_bookings_conf = 0;
}
sub debug_whats_changed {
d(<]/) {
if ($name !~ /\.\.\.$/) { msg("That text - $name - is not suitable; please don't use apostrophe, quote, ampersand, etc."); }
return 0;
}
return 1;
}
# if we're in the past, load the old timetable from that time
# from the first backups/YYYYMMDD after the last day of that week
# this is "a bit of a hack" :)
sub load_timetable_from_backup {
opendir BACKUP_DIR, "backups";
my @dates = grep {/^\d{8}$/} sort readdir BACKUP_DIR; # year 10000 bug lol
closedir BACKUP_DIR;
my $last_day_of_week_date = $weekday_dates[$max_wday]->yyyymmdd;
# msg("last_day_of_week is $friday_date");
for my $date (@dates) {
if ($date gt $last_day_of_week_date) {
# msg("using backup timetable from $date");
my $x = $static_file;
local $static_file = "backups/$date/$x";
$x = $static_info_file;
local $static_info_file = "backups/$date/$x";
load_static();
load_static_info();
last;
}
}
# couldn't find a suitable backup, so assume same as current timetable
}
sub dump_as_tsv_or_excel {
my @headers = qw(Week Date Period Category Resource Person);
my $added_type_headers = 0;
my @table = map {
my ($date, $week, $ymd, $period, $cat, $resource, $who, $info_types, $info_values) = @$_;
$resource =~ s/.*? : //; # FIXME ?
$period =~ s/^Period //i; # FIXME ?
if (!$added_type_headers) {
if (!$page_bookings_show_all) { push @headers, @$info_types; }
$added_type_headers = 1;
}
[$week, $ymd, $period, $cat, $resource, $who, @$info_values]
} @$my_bookings;
if (param("tsv")) {
dump_as_tsv(\@headers, \@table);
} else {
dump_as_excel(\@headers, \@table);
}
return;
}
sub dump_as_tsv {
my ($headers, $table) = @_;
$content_type = "text/tab-separated-values";
for my $row ($headers, @$table) {
print join "\t", @$row;
print "\r\n";
}
}
sub dump_as_excel {
binmode(\*STDOUT);
my ($headers, $table) = @_;
$content_type = "application/vnd.ms-excel";
my $excel = Spreadsheet::SimpleExcel->new();
$excel->add_worksheet('bookings',{-headers => $headers, -data => $table});
out($excel->output_as_string());
}
sub erase_the_timetable {
assume_nothing_changed(); # not needed?
@$static = ();
@$static_info = ();
$changed_static = 1;
$changed_static_info = 1;
write_files_if_necessary();
}
# fancy dropdown ----------------------------
sub fancy_select {
# DODGY :)
my $o = "";
redirect_out(\$o, \&fancy_select_, @_);
return $o;
}
sub fancy_select_ {
my $small_menu_width = "100%";
my $visible_rows = 16;
my %opts = ref $_[0] ? %{shift @_} : ();
my ($name, $ary) = @_;
$ary = [@$ary];
# my @attrs = $opts{attrs} ? ($opts{attrs}) : ();
my @attrs = ();
# FIXME this dropdown function should be nice
my $tf_size = delete $opts{size}; dflt($tf_size, 11);
if (delete $opts{autosubmit}) { push @attrs, onchange=>"this.form.submit();"; }
# if (delete $opts{enter_submits}) { push @attrs, onKeyPress=>"enter_submits(this, event);"; }
# doesn't work!
# if (delete $opts{dynamic_expand}) {
# $opts{clip} = 1;
# push @attrs,
# onFocus=>"hover_select_focus(this);", onBlur=>"hover_select_blur(this)", onClick=>"hover_select_click(this)", onMouseover=>"hover_select_mouseover(this);", onMouseout=>"hover_select_mouseout(this)";
## qq{onClick="alert('You are going too quick, take it easy!')" onMouseover="expand_select_delay(this,event);" onFocus="expand_select_focus(this,event);"};
# # see common.js
# # onMouseout="this.style.width='$small_menu_width';"}; }
# # this.style.width='';
# };
if (delete $opts{clip}) { push @attrs, style=>"width: $small_menu_width; text-overflow: clip; overflow: hidden;"; }
my $selected_option = delete $opts{selected};
my $prompt = delete $opts{prompt} || "click to select...";
my $no_prompt = delete $opts{no_prompt};
my $abbrev = delete $opts{abbrev};
my $n_opts_inc_prompt = @$ary;
if (!$no_prompt) {
++$n_opts_inc_prompt
}
if ($visible_rows > $n_opts_inc_prompt) { $visible_rows = $n_opts_inc_prompt }
# rest of opts are to add as html to the select, ok? bogus, huh :)
push @attrs, @{sort_hash(\%opts)};
my $initial_text = "";
if (defined $selected_option) {
$initial_text = $selected_option;
} elsif (!$no_prompt && !defined $selected_option) {
$initial_text = $prompt;
}
$initial_text = encode_entities($initial_text);
# TODO should encode_entities?
my $name_squot = squot_str($name);
# Tag("span", style=>"vertical-align: middle");
# Tag("input", name=>$name, type=>"text", size=>$tf_size, onFocus=>"fancy_select_open($name_squot);", readonly=>"1", style=>"vertical-align: middle");
Tag("input", name=>$name, type=>"text", style=>"width: ".($tf_size*9).";", onFocus=>"fancy_select_open($name_squot);", readonly=>"1", style=>"vertical-align: middle", value=>"$initial_text");
# Tag("span", style=>"position: relative; top: 4px; left: -2px;", border=>0, onclick=>"fancy_select_open($name_squot);"); #, width=>18, height=>22, style="position: 3"); # program / function to do this?
Tag("img", src=>"/sam/dropdown.gif", width=>18, height=>22, border=>0, onclick=>"fancy_select_open($name_squot);", style=>"vertical-align: middle"); #, width=>18, height=>22, style="position: 3"); # program / function to do this?
# Tag("img", src=>"/sam/dropdown.gif", width=>18, height=>22, style=>"position: relative; top: 4px; left: -2px;", border=>0, onclick=>"fancy_select_open($name_squot);"); #, width=>18, height=>22, style="position: 3"); # program / function to do this?
# Ctag("span");
Tag("span", id=>"FS$name", style=>"display: none; position: absolute;");
Tag("select", name=>"FSS$name", multiple=>1, size=>$visible_rows, onBlur=>"fancy_select_close(1)", onKeyPress=>"fancy_select_enter(event);", onClick=>"fancy_select_click();", @attrs);
# Tag("select", multiple=>1, size=>$fancy_select_visible_rows, name=>$name, @attrs);
my $selected = !defined $selected_option ? " selected" : "";
if (!$no_prompt) {
out(qq{});
$selected = "";
}
while (@$ary) {
my ($value, $label);
if ($abbrev) { ($value, $label) = splice @$ary, 0, 2; }
else { $value = shift @$ary; $label = $value; }
if (defined $selected_option) {
$selected = $value eq $selected_option ? " selected" : "";
}
if ($label eq $value) {
out(qq{});
} else {
out(qq{});
}
if (!defined $selected_option) {
$selected = "";
}
}
Ctag("select");
Ctag("span");
}
# not done yet!
#sub admin_selector_shared_popup {
# my $admin_select_visible_rows = 24;
# Tag("select", id=>"adminselector", multiple=>1, size=>$admin_select_visible_rows, onBlur=>"fancy_select_close();");
# out(' ');
# Ctag("select");
#}
sub admin_selector {
if ($available) {
# if ($mode_admin_any) { out(" | "); }
my $options = ['', @$teacher_ids];
if ($static_booker) {
unshift @$options, $static_booker;
}
if (!$mine && $id ne $static_booker) {
unshift @$options, $id;
}
my $abbrev;
@$options = map { $_, $_ eq "" ? "" : "$_ $teacher_names->{$_}" } @$options; $abbrev = 1;
# @$options = map { $_, $_ eq "" ? "" : $teacher_names->{$_} || $_ } @$options; $abbrev = 1;
my $tiny = $booker eq "";
my $size = 7;
my $mangle_factor = $screenwidth < 1024 ? 1 : 1.40;
$size *= $mangle_factor;
my $dropdown_opts = {
no_prompt=>1, abbrev=>$abbrev, selected=>$booker||'',
clip=>1, size=>$size, #$class ? 8 : 4, # COMPACTING
# enter_submits => 1,
# dynamic_expand=>1,
};
# out(qq{ }.dropdown($dropdown_opts, $cell_name, $options).qq{ });
out(fancy_select($dropdown_opts, $cell_name, $options));
} else {
checkbox_selector();
}
}
|