Annotation of loncom/interface/loncourserespicker.pm, revision 1.1
1.1 ! raeburn 1: # The LearningOnline Network
! 2: #
! 3: # $Id: loncourserespicker.pm,v 1.1 2012/03/17 20:51:01 raeburn Exp $
! 4: #
! 5: # Copyright Michigan State University Board of Trustees
! 6: #
! 7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
! 8: #
! 9: # LON-CAPA is free software; you can redistribute it and/or modify
! 10: # it under the terms of the GNU General Public License as published by
! 11: # the Free Software Foundation; either version 2 of the License, or
! 12: # (at your option) any later version.
! 13: #
! 14: # LON-CAPA is distributed in the hope that it will be useful,
! 15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
! 16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
! 17: # GNU General Public License for more details.
! 18: #
! 19: # You should have received a copy of the GNU General Public License
! 20: # along with LON-CAPA; if not, write to the Free Software
! 21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
! 22: #
! 23: # /home/httpd/html/adm/gpl.txt
! 24: #
! 25: # http://www.lon-capa.org/
! 26: #
! 27:
! 28: =pod
! 29:
! 30: =head1 NAME
! 31:
! 32: loncourserespicker - Utilities to choose folders and resources in a course.
! 33:
! 34: =head1 SYNOPSIS
! 35:
! 36: loncourserespicker provides an interface for selecting which folders and/or
! 37: resources are to be either:
! 38:
! 39: (a) exported to an IMS Content Package
! 40: (b) subject to access blocking for the duriation of an exam/quiz.
! 41:
! 42: =head1 DESCRIPTION
! 43:
! 44: This module provides routines to generate a hierarchical display of folders
! 45: and resources in a course which can be selected for specific actions.
! 46:
! 47: The choice of items is copied back to the main window from which the pop-up
! 48: window used to display the Course Contents was opened.
! 49:
! 50: =head1 OVERVIEW
! 51:
! 52: The main subroutine: &create_picker() will display the hierarchy of folders,
! 53: sub-folders, and resources in the Main Course Documents area. Items can be
! 54: selected using checkboxes, and/or a "Check All" button. Selection of a folder
! 55: causes the contents of the folder to also be selected automatically. The
! 56: propagation of check status is recursive into sub-folders. Likewise, if an
! 57: item deep in a nested set of folders and sub-folders is unchecked, the
! 58: uncheck will propagate up through the hierarchy causing any folders at
! 59: a higher level to become unchecked.
! 60:
! 61: There is a submit button, which will be named differently according to the
! 62: content in which resource/folder selection is being made.
! 63:
! 64: The two contexts currently supported are: IMS export and selection of
! 65: content to be subject to access restructions for the duration of an
! 66: exam.
! 67:
! 68: =head1 INTERNAL SUBROUTINES
! 69:
! 70: =item &create_picker()
! 71:
! 72: Created HTML mark up to display contents of course with checkboxes to
! 73: select items. Checking a folder causes recursive checking of items
! 74: within the folder. Unchecking a resource causing unchecking of folders
! 75: containing the item back up to the top level.
! 76:
! 77: Inputs: 7.
! 78: - $navmap -- Reference to LON-CAPA navmap object
! 79: (encapsulates information about resources in the course).
! 80:
! 81: - $context -- Context in which course resource selection is being made.
! 82: Currently imsexport and examblock are supported.
! 83:
! 84: - $formname -- Name of the form in the window from which the pop-up
! 85: used to select course items was launched.
! 86:
! 87: - $crstype -- Course or Community
! 88:
! 89: - $blockedmaps -- Reference to hash of previously selected maps
! 90: (e.g., for a live exam block).
! 91:
! 92: - $blockedresources -- Reference to hash of resources selected
! 93: previously (e.g., for an exam block).
! 94:
! 95: - $block -- An internal ID (integer) used to track which exam
! 96: block currently being configured.
! 97:
! 98:
! 99: Output: $output is the HTML mark-up for display/selection of content
! 100: items in the pop-up window.
! 101:
! 102: =item &respicker_javascript()
! 103:
! 104: Creates javascript functions for checking/unchecking all items, and
! 105: for recursive checking triggered by checking a folder, or recursive
! 106: (upeards) unchecking of an item within a folder.
! 107:
! 108: Inputs: 7.
! 109: - $startcount -- Starting offset of form element numbering for items
! 110:
! 111: - $numcount -- Total numer of folders and resources in course.
! 112:
! 113: - $context -- Context in which resources are being displayed
! 114: (imsexport or examblock).
! 115:
! 116: - $formname -- Name of form.
! 117:
! 118: - $children -- Reference to hash of items contained within a folder.
! 119:
! 120: - $hierarchy -- Reference to hierarchy of folders containing an item.
! 121:
! 122: - $checked_maps -- Reference to array of folders currently checked.
! 123:
! 124: Output: 1. Javascript (witthin <script></script> tags.
! 125:
! 126:
! 127: =item &get_navmap_object()
! 128:
! 129: Instantiates a navmaps object, and generates an error message if
! 130: no object instantiated.
! 131:
! 132: Inputs: 2.
! 133: - $crstype -- Container type: Course or Community
! 134:
! 135: - $context -- Context: imsexport or examblock
! 136:
! 137: =over
! 138:
! 139: =back
! 140:
! 141: =cut
! 142:
! 143:
! 144: package Apache::loncourserespicker;
! 145:
! 146: use strict;
! 147: use Apache::lonnet;
! 148: use Apache::loncommon;
! 149: use Apache::lonhtmlcommon;
! 150: use Apache::lonnavmaps;
! 151: use Apache::lonlocal;
! 152: use LONCAPA qw(:DEFAULT :match);
! 153:
! 154: sub create_picker {
! 155: my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block) = @_;
! 156: return unless (ref($navmap));
! 157: my ($it,$output,$numdisc,%maps,%resources,%discussiontime,%currmaps,%currresources);
! 158: $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
! 159: if (ref($blockedmaps) eq 'HASH') {
! 160: %currmaps = %{$blockedmaps};
! 161: }
! 162: if (ref($blockedresources) eq 'HASH') {
! 163: %currresources = %{$blockedresources};
! 164: }
! 165: my @checked_maps;
! 166: my $curRes;
! 167: my $numprobs = 0;
! 168: my $depth = 0;
! 169: my $count = 0;
! 170: my $boards = 0;
! 171: my $startcount = 1;
! 172: my %parent = ();
! 173: my %children = ();
! 174: my %hierarchy = ();
! 175: my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
! 176: my $whitespace =
! 177: '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />';
! 178:
! 179: my $onsubmit;
! 180: if ($context eq 'examblock') {
! 181: my $maps_elem = 'docs_maps_'.$block;
! 182: my $res_elem = 'docs_resources_'.$block;
! 183: $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"';
! 184: }
! 185: my $display =
! 186: '<form name="'.$formname.'" action="" method="post"'.$onsubmit.'>'."\n".
! 187: '<p>';
! 188: if ($context eq 'imsexport') {
! 189: $display .= &mt('Choose which items you wish to export from your '.
! 190: $crstype.'.');
! 191: $startcount = 5;
! 192: } elsif ($context eq 'examblock') {
! 193: $display .= &mt('Items in '.lc($crstype).' for which access will be blocked.');
! 194: }
! 195: $display .= '</p>';
! 196: if ($context eq 'imsexport') {
! 197: $display .= '<div class="LC_columnSection">'."\n".
! 198: '<fieldset>'.
! 199: '<legend>'.&mt('Content items').'</legend>'."\n";
! 200: }
! 201: $display .=
! 202: '<input type="button" value="'.&mt('check all').'" '.
! 203: 'onclick="javascript:checkAll(document.'.$formname.'.archive)" />'.
! 204: ' <input type="button" value="'.&mt('uncheck all').'"'.
! 205: ' onclick="javascript:uncheckAll(document.'.$formname.'.archive)" />';
! 206: if ($context eq 'imsexport') {
! 207: $display .= '</fieldset>';
! 208: %discussiontime =
! 209: &Apache::lonnet::dump('discussiontimes',
! 210: $env{'course.'.$env{'request.course.id'}.'.domain'},
! 211: $env{'course.'.$env{'request.course.id'}.'.num'});
! 212: $numdisc = keys(%discussiontime);
! 213: if ($numdisc > 0) {
! 214: $display .=
! 215: '<fieldset>'.
! 216: '<legend>'.&mt('Discussion posts').'</legend>'.
! 217: '<input type="button" value="'.&mt('check all').'"'.
! 218: ' onclick="javascript:checkAll(document.'.$formname.'.discussion)" />'.
! 219: ' <input type="button" value="'.&mt('uncheck all').'"'.
! 220: ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'.
! 221: '</fieldset></div>';
! 222: }
! 223: }
! 224: my $curRes;
! 225: my $lastcontainer = $startcount;
! 226: $display .= &Apache::loncommon::start_data_table()
! 227: .&Apache::loncommon::start_data_table_header_row();
! 228: if ($context eq 'imsexport') {
! 229: $display .= '<th>'.&mt('Export content item?').'</th>';
! 230: if ($numdisc > 0) {
! 231: $display .= '<th>'.&mt('Export discussion posts?').'</th>';
! 232: }
! 233: } elsif ($context eq 'examblock') {
! 234: $display .= '<th>'.&mt('Access blocked?').'</th>';
! 235: }
! 236: $display .= &Apache::loncommon::end_data_table_header_row();
! 237: while ($curRes = $it->next()) {
! 238: if (ref($curRes)) {
! 239: $count ++;
! 240: }
! 241: if ($curRes == $it->BEGIN_MAP()) {
! 242: $depth++;
! 243: $parent{$depth} = $lastcontainer;
! 244: }
! 245: if ($curRes == $it->END_MAP()) {
! 246: $depth--;
! 247: $lastcontainer = $parent{$depth};
! 248: }
! 249: if (ref($curRes)) {
! 250: my $symb = $curRes->symb();
! 251: my $ressymb = $symb;
! 252: if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) {
! 253: unless ($ressymb =~ m|adm/wrapper/adm|) {
! 254: $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
! 255: }
! 256: }
! 257: my $currelem = $count+$boards+$startcount;
! 258: $display .= &Apache::loncommon::start_data_table_row().
! 259: '<td>'."\n".
! 260: '<input type="checkbox" name="archive" value="'.$count.'" ';
! 261: if (($curRes->is_sequence()) || ($curRes->is_page())) {
! 262: $lastcontainer = $currelem;
! 263: $display .= 'onclick="javascript:checkFolder(this.form,'."'$currelem'".')" ';
! 264: my $mapurl = (&Apache::lonnet::decode_symb($symb))[2];
! 265: if ($currmaps{$mapurl}) {
! 266: $display .= 'checked="checked"';
! 267: push(@checked_maps,$currelem);
! 268: }
! 269: } else {
! 270: if ($curRes->is_problem()) {
! 271: $numprobs ++;
! 272: }
! 273: $display .= 'onclick="javascript:checkResource(this.form,'."'$currelem'".')" ';
! 274: if ($currresources{$symb}) {
! 275: $display .= 'checked="checked"';
! 276: }
! 277: }
! 278: $display .= ' />'."\n";
! 279: for (my $i=0; $i<$depth; $i++) {
! 280: $display .= "$whitespace\n";
! 281: }
! 282: my $icon = 'src="'.$location.'/unknown.gif" alt=""';
! 283: if ($curRes->is_sequence()) {
! 284: $icon = 'src="'.$location.'/navmap.folder.open.gif" alt="'.&mt('"Folder').'"';
! 285: } elsif ($curRes->is_page()) {
! 286: $icon = 'src="'.$location.'/navmap.page.open.gif" alt="'.&mt('Composite Page').'"';
! 287: } elsif ($curRes->is_problem()) {
! 288: $icon = 'src="'.$location.'/problem.gif" alt="'.&mt('Problem').'"';
! 289: } elsif ($curRes->is_task()) {
! 290: $icon = 'src="'.$location.'/task.gif" alt="'.&mt('Task').'"';
! 291: } elsif ($curRes->src ne '') {
! 292: $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
! 293: }
! 294: $display .= '<img '.$icon.' /> '."\n";
! 295: $children{$parent{$depth}} .= $currelem.':';
! 296: if ($context eq 'examblock') {
! 297: if ($parent{$depth} > 1) {
! 298: if ($hierarchy{$parent{$depth}}) {
! 299: $hierarchy{$currelem} = $hierarchy{$parent{$depth}}.",'$parent{$depth}'";
! 300: } else {
! 301: $hierarchy{$currelem} = "'$parent{$depth}'";
! 302: }
! 303: }
! 304: }
! 305: $display .= ' '.$curRes->title().'</td>'."\n";
! 306:
! 307: if ($context eq 'imsexport') {
! 308: # Existing discussion posts?
! 309: if ($discussiontime{$ressymb} > 0) {
! 310: $boards ++;
! 311: $display .= '<td align="right">'
! 312: .'<input type="checkbox" name="discussion" value="'.$count.'" />'
! 313: .'</td>'."\n";
! 314: } elsif ($numdisc > 0) {
! 315: $display .= '<td> </td>'."\n";
! 316: }
! 317: }
! 318: $display .= &Apache::loncommon::end_data_table_row();
! 319: }
! 320: }
! 321: $display .= &Apache::loncommon::end_data_table();
! 322: if ($context eq 'imsexport') {
! 323: if ($numprobs > 0) {
! 324: $display .= '<p><span class="LC_nobreak">'.
! 325: &mt('Export format for LON-CAPA problems:').
! 326: '<label><input type="radio" name="format" value="xml" checked="checked" />'.
! 327: ' '.&mt('XML').'</label>'.(' ' x3).
! 328: '<label><input type="radio" name="format" value="html" />'.
! 329: ' '.&mt('HTML').'</label>'.(' ' x3).
! 330: '<label><input type="radio" name="format" value="plaintext" />'.
! 331: ' '.&mt('Text').'</label></span></p>';
! 332: }
! 333: }
! 334: $display .= '<p>';
! 335: if ($context eq 'imsexport') {
! 336: $display .=
! 337: '<input type="hidden" name="finishexport" value="1" />'.
! 338: '<input type="submit" name="exportcourse" value="'.
! 339: &mt('Export').'" />';
! 340: } elsif ($context eq 'examblock') {
! 341: $display .=
! 342: '<input type="submit" name="resourceblocks" value="'.
! 343: &mt('Copy Choices to Main Window ').'" />';
! 344: }
! 345: $display .= '</p></form>';
! 346: my $numcount = $count + $boards + $startcount;
! 347: my $scripttag =
! 348: &respicker_javascript($startcount,$numcount,$context,$formname,\%children,
! 349: \%hierarchy,\@checked_maps);
! 350: my ($title,$crumbs,$args);
! 351: if ($context eq 'imsexport') {
! 352: $title = 'Export '.$crstype.' to IMS Package';
! 353: } elsif ($context eq 'examblock') {
! 354: $title = 'Resources with Access blocked';
! 355: $args = {'only_body' => 1,
! 356: 'add_entries' => { onload => 'javascript:recurseFolders();' },
! 357: };
! 358: }
! 359: my $output = &Apache::loncommon::start_page($title,$scripttag,$args);
! 360: if ($context eq 'imsexport') {
! 361: $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export');
! 362: }
! 363: $output .= $display;
! 364: if ($context eq 'examblock') {
! 365: $output .= &Apache::loncommon::end_page();
! 366: }
! 367: return $output;
! 368: }
! 369:
! 370: sub respicker_javascript {
! 371: my ($startcount,$numitems,$context,$formname,$children,$hierarchy,
! 372: $checked_maps) = @_;
! 373: return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH')
! 374: && (ref($checked_maps) eq 'ARRAY'));
! 375: my $scripttag = <<"START";
! 376: <script type="text/javascript">
! 377: // <![CDATA[
! 378: function checkAll(field) {
! 379: if (field.length > 0) {
! 380: for (i = 0; i < field.length; i++) {
! 381: field[i].checked = true ;
! 382: }
! 383: } else {
! 384: field.checked = true
! 385: }
! 386: }
! 387:
! 388: function uncheckAll(field) {
! 389: if (field.length > 0) {
! 390: for (i = 0; i < field.length; i++) {
! 391: field[i].checked = false;
! 392: }
! 393: } else {
! 394: field.checked = false;
! 395: }
! 396: }
! 397:
! 398: function checkFolder(form,item) {
! 399: if (form.elements[item].checked == true) {
! 400: containerCheck(form,item);
! 401: } else {
! 402: containerUncheck(form,item);
! 403: }
! 404: }
! 405:
! 406: function checkResource(form,item) {
! 407: if (form.elements[item].checked == false) {
! 408: containerUncheck(form,item);
! 409: }
! 410: }
! 411:
! 412: numitems = $numitems;
! 413: var parents = new Array(numitems);
! 414: var nesting = new Array(numitems);
! 415: var initial = new Array();
! 416: for (var i=$startcount; i<numitems; i++) {
! 417: parents[i] = new Array();
! 418: nesting[i] = new Array();
! 419: }
! 420:
! 421: START
! 422:
! 423: foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
! 424: my @contents = split(/:/,$children->{$container});
! 425: for (my $i=0; $i<@contents; $i ++) {
! 426: $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
! 427: }
! 428: }
! 429:
! 430: if ($context eq 'examblock') {
! 431: foreach my $item (sort { $a <=> $b } (keys(%{$hierarchy}))) {
! 432: $scripttag .= "nesting[$item] = new Array($hierarchy->{$item});\n";
! 433: }
! 434:
! 435: my @sorted_maps = sort { $a <=> $b } (@{$checked_maps});
! 436: for (my $i=0; $i<@sorted_maps; $i++) {
! 437: $scripttag .= "initial[$i] = '$sorted_maps[$i]'\n";
! 438: }
! 439: $scripttag .= <<"EXTRA";
! 440:
! 441: function recurseFolders() {
! 442: if (initial.length > 0) {
! 443: for (var i=0; i<initial.length; i++) {
! 444: containerCheck(document.$formname,initial[i]);
! 445: }
! 446: }
! 447: return;
! 448: }
! 449:
! 450: EXTRA
! 451: }
! 452:
! 453: $scripttag .= <<"END";
! 454:
! 455: function containerCheck(form,item) {
! 456: form.elements[item].checked = true;
! 457: if(Object.prototype.toString.call(parents[item]) === '[object Array]') {
! 458: if (parents[item].length > 0) {
! 459: for (var j=0; j<parents[item].length; j++) {
! 460: containerCheck(form,parents[item][j]);
! 461: }
! 462: }
! 463: }
! 464: }
! 465:
! 466: function containerUncheck(form,item) {
! 467: if(Object.prototype.toString.call(nesting[item]) === '[object Array]') {
! 468: if (nesting[item].length > 0) {
! 469: for (var i=0; i<nesting[item].length; i++) {
! 470: form.elements[nesting[item][i]].checked = false;
! 471: }
! 472: }
! 473: }
! 474: return;
! 475: }
! 476:
! 477: END
! 478:
! 479: if ($context eq 'examblock') {
! 480: $scripttag .= <<ENDEX;
! 481: function writeToOpener(maps,resources) {
! 482: var checkedmaps = '';
! 483: var checkedresources = '';
! 484: for (var i=0; i<document.$formname.archive.length; i++) {
! 485: if (document.$formname.archive[i].checked) {
! 486: var isResource = 1;
! 487: var include = 1;
! 488: var elemnum = i+1+$startcount;
! 489: if (Object.prototype.toString.call(parents[elemnum]) === '[object Array]') {
! 490: if (parents[elemnum].length > 0) {
! 491: isResource = 0;
! 492: }
! 493: }
! 494: if (isResource == 1) {
! 495: if (nesting[elemnum].length > 0) {
! 496: var lastelem = nesting[elemnum].length-1;
! 497: if (document.$formname.elements[nesting[elemnum][lastelem]].checked) {
! 498: include = 0;
! 499: }
! 500: }
! 501: }
! 502: if (include == 1) {
! 503: if (isResource == 1) {
! 504: checkedresources += document.$formname.archive[i].value+',';
! 505: } else {
! 506: checkedmaps += document.$formname.archive[i].value+',';
! 507: }
! 508: }
! 509: }
! 510: }
! 511: opener.document.getElementById(maps).value = checkedmaps;
! 512: opener.document.getElementById(resources).value = checkedresources;
! 513: window.close();
! 514: return false;
! 515: }
! 516:
! 517: ENDEX
! 518: }
! 519:
! 520: $scripttag .= '
! 521: // ]]>
! 522: </script>
! 523: ';
! 524: return $scripttag;
! 525: }
! 526:
! 527: sub get_navmap_object {
! 528: my ($crstype,$context) = @_;
! 529: my $navmap = Apache::lonnavmaps::navmap->new();
! 530: my $outcome;
! 531: if (!defined($navmap)) {
! 532: if ($context eq 'imsexport') {
! 533: $outcome = &Apache::loncommon::start_page('Export '.$crstype.' to IMS Package').
! 534: '<h2>'.&mt('IMS Export Failed').'</h2>';
! 535: } elsif ($context eq 'examblock') {
! 536: $outcome = &Apache::loncommon::start_page('Selection of Resources for Blocking',
! 537: undef,{'only_body' => 1,}).
! 538: '<h2>'.&mt('Resource Display Failed').'</h2>';
! 539: }
! 540: $outcome .= '<div class="LC_error">';
! 541: if ($crstype eq 'Community') {
! 542: $outcome .= &mt('Unable to retrieve information about community contents');
! 543: } else {
! 544: $outcome .= &mt('Unable to retrieve information about course contents');
! 545: }
! 546: $outcome .= '</div>';
! 547: if ($context eq 'imsexport') {
! 548: $outcome .= '<a href="/adm/coursedocs">';
! 549: if ($crstype eq 'Community') {
! 550: $outcome .= &mt('Return to Community Editor');
! 551: } else {
! 552: $outcome .= &mt('Return to Course Editor');
! 553: }
! 554: $outcome .= '</a>';
! 555: &logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
! 556: } elsif ($context eq 'examblock') {
! 557: $outcome .= '<href="javascript:window.close();">'.&mt('Close window').'</a>';
! 558: }
! 559: return (undef,$outcome);
! 560: } else {
! 561: return ($navmap);
! 562: }
! 563: }
! 564:
! 565: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>