create and upload fieldbook phenotypes with treatments
[sgn.git] / lib / CXGN / Page / Widgets.pm
blobd79909096b054e0c481839bbe99acebdd3b68ba4
1 #!/usr/bin/perl
3 package CXGN::Page::Widgets;
5 use strict;
6 use Carp;
7 #use CXGN::Login;
8 use CXGN::Cookie;
9 use CXGN::Page::UserPrefs;
11 use CXGN::Scrap;
13 =head1 NAME
15 CXGN::Page::Widget - helper functions for creating dynamic page elements such
16 as collapsible boxes, content swappers, and (moveable windows?).
18 =head1 SYNOPSIS
20 Built-in support for cookie-setting will give the user fine control over his or her
21 SGN experience.
23 The basic requirement for something to be a widget and not a FormattingHelper is
24 that it is dynamic.
26 =head1 FUNCTIONS
28 All functions are EXPORT_OK.
31 =cut
32 BEGIN {
33 our @ISA = qw/Exporter/;
34 use Exporter;
35 our $VERSION = sprintf "%d.%03d", q$Revision: 1.1 $ =~ /(\d+)/g;
36 our @EXPORT_OK = qw/collapser swapper cycler/;
38 our @ISA;
39 our @EXPORT_OK;
41 =head2 collapser
43 Returns a link and target content, where use of the link by the user
44 will cause that content to collapse.
47 Args: Pass a hash reference with the following keys:
48 linktext: text of the link, which will be prepended with (+) or (-)
49 hide_state_linktext: text of link when hidden, if this is not specified, prepending will be used
50 content: the content that you want to be hidden
51 id: a unique id of your choice, which will serve as part of the user's cookie prefs
52 linkstyle(optional): style of the controller link
53 collapsed: default state is: already collapsed
54 save: the state is savable to the user preference string. If you use this feature,
55 you MUST add the id to the UserPref/Registry
56 alt_href: go to page if javascript not working
57 alt_target: target for alt_href
59 Returns: ($link, $content), where $link is the html for the two consecutive <a>links</a> that make the button, and $content is the content you provided,
60 wrapped with <span id='[provided id]'></span>
62 Usage: my ($link, $content) = collapser({
63 linktext => 'Heading',
64 linkstyle => 'text-decoration:none',
65 content => '<b>Hide this text on link-click</b>',
66 collapsed => 0,
67 save => 1, #uses CXGN::UserPrefs, the "id" key on the
68 #next line would have to be in the Register
69 id=>'hiderbox1'
70 });
71 print $link . "<br>" . $content;
73 =cut
75 sub collapser {
76 my %args = %{ shift @_ };
78 _check_args({
79 args => \%args,
80 valid => ['linktext', 'hide_state_linktext', 'linkstyle', 'content', 'id', 'collapsed', 'save', 'alt_href', 'alt_target'],
81 required => ['linktext', 'content', 'id']
82 });
84 my ($linktext, $hide_state_linktext, $content, $id, $alt_href, $alt_target)
85 = ($args{'linktext'}, $args{'hide_state_linktext'}, $args{'content'}, $args{'id'}, $args{'alt_href'}, $args{'alt_target'});
86 _check_id($id) if $args{save};
88 my $state = "";
89 $state = _get_pref($id) if $args{save};
90 unless($state){
91 if($args{collapsed}){
92 $state = "hid"
95 my ($on_display, $off_display) = ("", "");
96 if($state eq "hid") { $on_display = "display:none;"; }
97 else { $off_display = "display:none;" }
99 my $linkstyle = $args{'linkstyle'} || "";
100 $linkstyle =~ s/;\s*$//;
101 $linkstyle .= ";";
102 my ($hide_save_js, $show_save_js) = ("","");
103 if($args{save}){
104 $hide_save_js = "UserPrefs.set(\"$id\", \"hid\");UserPrefs.setCookie();";
105 $show_save_js = "UserPrefs.set(\"$id\", \"dsp\");UserPrefs.setCookie();";
107 $hide_state_linktext ||= $linktext;
108 no warnings 'uninitialized';
109 my $link = <<HTML;
110 <a class="collapser collapser_show" target="$alt_target" href="$alt_href" style="$linkstyle;$on_display" onclick="
111 Effects.swapElements('${id}_offswitch', '${id}_onswitch');
112 Effects.hideElement('${id}_content');
113 $hide_save_js
114 return false;"
115 id="${id}_offswitch"><img class="collapser_img" src="/documents/img/collapser_minus.png" />$linktext</a>
116 <a class="collapser collapser_show" target="$alt_target" href="$alt_href" style="$linkstyle;$off_display" onclick="
117 Effects.swapElements('${id}_onswitch', '${id}_offswitch');
118 Effects.showElement('${id}_content');
119 $show_save_js
120 return false;"
121 id="${id}_onswitch"><img class="collapser_img" src="/documents/img/collapser_plus.png" />$hide_state_linktext</a>
122 HTML
124 my $wrapped_content = qq|<span id="${id}_content" style="$on_display">$content</span>|;
126 return ($link, $wrapped_content);
129 =head2 swapper
131 Returns a link and target content, where use of the link by the user
132 will cause that content to switch to alternate content (back-and-forth), which will be displayed in a consecutively hidden fashion
134 Args:
136 Returns: ($link, $content), where $link is the html for the two consecutive <a>links</a> that make the button, and $content contains consecutive
137 contents, one hidden, each wrapped with <span id=[provided id]:(def|alt)></span>
138 Usage: my ($link, $content) = swapper ({
139 linktext => 'Swap With Something Else',
140 linktext_alt => 'Unswap me you savage!",
141 linkstyle => 'text-decoration:none',
142 content => 'I am the regular content',
143 content_alt => 'I am the alternate content <a href='www.boycott-riaa.org'>Click me!</a>',
144 id=>'swapper1'
146 print $link . "<br>" . $content;
148 =cut
150 sub swapper {
151 my %args = %{ shift @_ };
152 #check arguments. i think this is better for everyone except beth
153 my @valid_keys = qw/linktext linktext_alt linkstyle content content_alt id/;
154 my @required_keys = qw/linktext linktext_alt content content_alt id/;
155 foreach my $argname (keys %args) {
156 grep {$_ eq $argname} @valid_keys
157 or croak "Unknown argument name '$argname' to collapser";
159 foreach my $required (@required_keys) {
160 grep {$_ eq $required} (keys %args)
161 or croak "Required key: '$required' not specified";
163 my ($linktext, $linktext_alt, $content, $content_alt, $id) = ($args{'linktext'}, $args{'linktext_alt'}, $args{'content'}, $args{'content_alt'}, $args{'id'});
164 my $linkstyle = $args{'linkstyle'} || "";
165 _check_id($id);
166 my $state = _get_pref($id);
167 my ($def_display, $alt_display) = ("", "");
168 if($state eq "alt") { $def_display = "display:none"; }
169 else { $alt_display = "display:none"; }
170 $linkstyle =~ s/;\s*$//g;
172 my $link = <<HTML;
173 <a href='#' style='$linkstyle;$def_display' onclick='
174 Effects.swapElements("${id}_swap", "${id}_unswap");
175 Effects.swapElements("${id}_def", "${id}_alt");
176 UserPrefs.set("$id", "alt");
177 UserPrefs.setCookie();
178 return false;'
179 id='${id}_swap'>$linktext</a>
180 <a href='#' style='$linkstyle;$alt_display', onclick='
181 Effects.swapElements("${id}_unswap", "${id}_swap");
182 Effects.swapElements("${id}_alt", "${id}_def");
183 UserPrefs.set("$id", "def");
184 UserPrefs.setCookie();
185 return false;'
186 id='${id}_unswap'>$linktext_alt</a>
187 HTML
189 my $wrapped_content = "<span id='${id}_def' style='$def_display'>$content</span><span id='${id}_alt' style='$alt_display'>$content_alt</span>";
190 return ($link, $wrapped_content);
193 =head2 cycler
195 Returns a link and target content, where use of the link by the user
196 will cause that content to cycle amongst alternate content, which will be displayed in a consecutively hidden fashion
198 Args: a hash reference with the keys:
199 id: a unique alphanumeric key
200 linktexts: an array reference containing the various texts (or html) to display as the cycler button link
201 linkstyle: (optional) a string containing common style for the link
202 contents: an array reference containing the contents to be cycled, in HTML format
204 Returns: ($link, $content), where $link is the html for the two consecutive <a>links</a> that make the button, and $content contains consecutive
205 contents, all but one hidden, each wrapped with <span id='[provided id]:(1|2|3...)'></span>
206 Usage: my ($link, $content) = cycler({
207 linktexts => ['Go to state two','Go to state three', 'Back to first state']
208 linkstyle => 'text-decoration:none',
209 contents => ['It's good to be state one!!', 'It's my turn, now, bitches!', 'State three is fine with me!']
210 id=>'cycler1'
212 print $link . "<br>" . $content;
214 =cut
216 sub cycler {
217 my %args = %{ shift @_ };
218 my @valid_keys = qw/linktexts linkstyle contents id/;
219 my @required_keys = qw/linktexts contents id/;
220 foreach my $argname (keys %args) {
221 grep {$_ eq $argname} @valid_keys
222 or croak "Unknown argument name '$argname' to collapser";
224 foreach my $required (@required_keys) {
225 grep {$_ eq $required} (keys %args)
226 or croak "Required key: '$required' not specified";
228 my ($linktexts, $contents) = ($args{'linktexts'}, $args{'contents'});
229 unless(@{$linktexts}==@{$contents} && @{$linktexts}>1) {
230 croak "'linktexts' array (@{$linktexts}) must have more than one element AND be equal in length to 'contents' array (size:@{$contents})";
232 my $id = $args{'id'};
234 my $linkstyle = $args{'linkstyle'} || "";
235 my $cyclesize = @{$contents};
237 my $link = "";
238 my $count = 0;
239 foreach my $linktext (@{$linktexts}) {
240 my $next = $count+1;
241 if($count >= ($cyclesize - 1)) {$next = 0;}
242 $link .= <<HTML;
243 <a href='#' style='$linkstyle' onclick='
244 Effects.swapElements("${id}_$count", "${id}_$next");
245 Effects.swapElements("${id}_control$count", "${id}_control$next");
246 return false;'
247 id='${id}_control$count'>$linktext</a>
248 HTML
249 $count++;
252 my $wrapped_content = "";
253 my $counter = 0;
254 foreach my $content (@{$contents}) {
255 $wrapped_content .= "<span id='${id}_$counter'>$content</span>";
256 $counter++;
258 return ($link, $wrapped_content);
261 =head2 notifier
263 Provides the html notifier box that corresponds with the notify() command in jslib: CXGN.Base. There should be one below every page toolbar, as far as I'm concerned ;)
265 Usage: print CXGN::Page::Widgets::notifier({style => "yaddayadda", append_style => "background-color:#fa0"});
267 Note: You can only print one notifier per page, as there is a pair of standard elementIds: SGN_NOTIFY_BOX and SGN_NOTIFY_CONTENT
269 =cut
271 sub notifier {
272 my $args = shift;
273 my $style= $args->{style};
274 $style ||= "width:350px; display:none; background-color:#fd6;border: #fa0 2px solid; text-align:left; padding:3px;";
275 my $append_style = $args->{append_style};
276 $append_style ||= "";
278 return <<HTML;
279 <div id='SGN_NOTIFY_BOX' style='${style}$append_style'>
280 <table style='width:100%; margin:0px; padding:0px;'><tr>
281 <td style='text-align:left'>
282 <a href='#' style='text-decoration:none' onclick='
283 document.getElementById("SGN_NOTIFY_BOX").style.display = "none";
284 document.getElementById("SGN_NOTIFY_CONTENT").innerHTML = "";'>(X)</a></td>
285 <td style='text-align:center'>
286 <span id='SGN_NOTIFY_CONTENT'>Notification Area</span>
287 </td>
288 <td style='text-align:right;visibility:hidden'>(X)</span> <!--balances close tick on left side so text appears centered-->
289 </tr></table>
290 </div>
291 HTML
294 # Dependent 'private' subroutines
295 sub _check_id {
296 my $id = shift;
297 die "No ID specified. You cannot use the save option without specifying a registered ID" unless ($id);
298 CXGN::Page::UserPrefs->validate_key($id);
301 sub _get_pref {
302 #since Widgets will be used after headers are sent, we shouldn't use the UserPrefs handle, but we can use the cookie string that we got from the handle when this module is called. It will be faster this way, anyhow.
303 my $name = shift;
304 my $cookie_string = CXGN::Cookie::get_cookie("user_prefs");
305 my ($value) = $cookie_string =~ /$name=([^:]+)/;
306 return $value;
309 sub _check_args {
310 my ($args) = shift;
311 my @valid = @{$args->{valid}};
312 my @required = @{$args->{required}};
313 my $args_hash_ref = $args->{args};
314 die "Key required: 'args'\n" unless($args_hash_ref);
316 if(@valid){
317 foreach my $argname (keys %{$args_hash_ref}) {
318 grep {$_ eq $argname} @valid
319 or croak "Unknown argument name '$argname' to collapser";
322 if(@required) {
323 foreach my $required (@required) {
324 grep {$_ eq $required} (keys %{$args_hash_ref})
325 or croak "Required key: '$required' not specified";
331 1;# do not remove
334 =head1 AUTHOR
336 Chris Carpita <csc32@cornell.edu>
338 =cut