[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-course-toolboxes', function (Y, NAME) { 2 3 /** 4 * Resource and activity toolbox class. 5 * 6 * This class is responsible for managing AJAX interactions with activities and resources 7 * when viewing a course in editing mode. 8 * 9 * @module moodle-course-toolboxes 10 * @namespace M.course.toolboxes 11 */ 12 13 // The CSS classes we use. 14 var CSS = { 15 ACTIVITYINSTANCE : 'activityinstance', 16 AVAILABILITYINFODIV : 'div.availabilityinfo', 17 CONTENTWITHOUTLINK : 'contentwithoutlink', 18 CONDITIONALHIDDEN : 'conditionalhidden', 19 DIMCLASS : 'dimmed', 20 DIMMEDTEXT : 'dimmed_text', 21 EDITINSTRUCTIONS : 'editinstructions', 22 EDITINGTITLE: 'editor_displayed', 23 HIDE : 'hide', 24 MODINDENTCOUNT : 'mod-indent-', 25 MODINDENTHUGE : 'mod-indent-huge', 26 MODULEIDPREFIX : 'module-', 27 SECTIONHIDDENCLASS : 'hidden', 28 SECTIONIDPREFIX : 'section-', 29 SHOW : 'editing_show', 30 TITLEEDITOR : 'titleeditor' 31 }, 32 // The CSS selectors we use. 33 SELECTOR = { 34 ACTIONAREA: '.actions', 35 ACTIONLINKTEXT : '.actionlinktext', 36 ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title', 37 ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form', 38 ACTIVITYICON : 'img.activityicon', 39 ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE, 40 ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a', 41 ACTIVITYLI : 'li.activity', 42 ACTIVITYTITLE : 'input[name=title]', 43 COMMANDSPAN : '.commands', 44 CONTENTAFTERLINK : 'div.contentafterlink', 45 CONTENTWITHOUTLINK : 'div.contentwithoutlink', 46 EDITTITLE: 'a.editing_title', 47 HIDE : 'a.editing_hide', 48 HIGHLIGHT : 'a.editing_highlight', 49 INSTANCENAME : 'span.instancename', 50 MODINDENTDIV : '.mod-indent', 51 MODINDENTOUTER : '.mod-indent-outer', 52 PAGECONTENT : 'body', 53 SECTIONLI : 'li.section', 54 SHOW : 'a.'+CSS.SHOW, 55 SHOWHIDE : 'a.editing_showhide' 56 }, 57 INDENTLIMITS = { 58 MIN: 0, 59 MAX: 16 60 }, 61 BODY = Y.one(document.body); 62 63 // Setup the basic namespace. 64 M.course = M.course || {}; 65 66 /** 67 * The toolbox class is a generic class which should never be directly 68 * instantiated. Please extend it instead. 69 * 70 * @class toolbox 71 * @constructor 72 * @protected 73 * @extends Base 74 */ 75 var TOOLBOX = function() { 76 TOOLBOX.superclass.constructor.apply(this, arguments); 77 }; 78 79 Y.extend(TOOLBOX, Y.Base, { 80 /** 81 * Send a request using the REST API 82 * 83 * @method send_request 84 * @param {Object} data The data to submit with the AJAX request 85 * @param {Node} [statusspinner] A statusspinner which may contain a section loader 86 * @param {Function} success_callback The callback to use on success 87 * @param {Object} [optionalconfig] Any additional configuration to submit 88 * @chainable 89 */ 90 send_request: function(data, statusspinner, success_callback, optionalconfig) { 91 // Default data structure 92 if (!data) { 93 data = {}; 94 } 95 // Handle any variables which we must pass back through to 96 var pageparams = this.get('config').pageparams, 97 varname; 98 for (varname in pageparams) { 99 data[varname] = pageparams[varname]; 100 } 101 102 data.sesskey = M.cfg.sesskey; 103 data.courseId = this.get('courseid'); 104 105 var uri = M.cfg.wwwroot + this.get('ajaxurl'); 106 107 // Define the configuration to send with the request 108 var responsetext = []; 109 var config = { 110 method: 'POST', 111 data: data, 112 on: { 113 success: function(tid, response) { 114 try { 115 responsetext = Y.JSON.parse(response.responseText); 116 if (responsetext.error) { 117 new M.core.ajaxException(responsetext); 118 } 119 } catch (e) {} 120 121 // Run the callback if we have one. 122 if (success_callback) { 123 Y.bind(success_callback, this, responsetext)(); 124 } 125 126 if (statusspinner) { 127 window.setTimeout(function() { 128 statusspinner.hide(); 129 }, 400); 130 } 131 }, 132 failure: function(tid, response) { 133 if (statusspinner) { 134 statusspinner.hide(); 135 } 136 new M.core.ajaxException(response); 137 } 138 }, 139 context: this 140 }; 141 142 // Apply optional config 143 if (optionalconfig) { 144 for (varname in optionalconfig) { 145 config[varname] = optionalconfig[varname]; 146 } 147 } 148 149 if (statusspinner) { 150 statusspinner.show(); 151 } 152 153 // Send the request 154 Y.io(uri, config); 155 return this; 156 } 157 }, 158 { 159 NAME: 'course-toolbox', 160 ATTRS: { 161 /** 162 * The ID of the Moodle Course being edited. 163 * 164 * @attribute courseid 165 * @default 0 166 * @type Number 167 */ 168 courseid: { 169 'value': 0 170 }, 171 172 /** 173 * The Moodle course format. 174 * 175 * @attribute format 176 * @default 'topics' 177 * @type String 178 */ 179 format: { 180 'value': 'topics' 181 }, 182 /** 183 * The URL to use when submitting requests. 184 * @attribute ajaxurl 185 * @default null 186 * @type String 187 */ 188 ajaxurl: { 189 'value': null 190 }, 191 /** 192 * Any additional configuration passed when creating the instance. 193 * 194 * @attribute config 195 * @default {} 196 * @type Object 197 */ 198 config: { 199 'value': {} 200 } 201 } 202 } 203 ); 204 205 /** 206 * Resource and activity toolbox class. 207 * 208 * This class is responsible for managing AJAX interactions with activities and resources 209 * when viewing a course in editing mode. 210 * 211 * @module moodle-course-toolboxes 212 * @namespace M.course.toolboxes 213 */ 214 215 /** 216 * Resource and activity toolbox class. 217 * 218 * This is a class extending TOOLBOX containing code specific to resources 219 * 220 * This class is responsible for managing AJAX interactions with activities and resources 221 * when viewing a course in editing mode. 222 * 223 * @class resources 224 * @constructor 225 * @extends M.course.toolboxes.toolbox 226 */ 227 var RESOURCETOOLBOX = function() { 228 RESOURCETOOLBOX.superclass.constructor.apply(this, arguments); 229 }; 230 231 Y.extend(RESOURCETOOLBOX, TOOLBOX, { 232 /** 233 * No groups are being used. 234 * 235 * @property GROUPS_NONE 236 * @protected 237 * @type Number 238 */ 239 GROUPS_NONE: 0, 240 241 /** 242 * Separate groups are being used. 243 * 244 * @property GROUPS_SEPARATE 245 * @protected 246 * @type Number 247 */ 248 GROUPS_SEPARATE: 1, 249 250 /** 251 * Visible groups are being used. 252 * 253 * @property GROUPS_VISIBLE 254 * @protected 255 * @type Number 256 */ 257 GROUPS_VISIBLE: 2, 258 259 /** 260 * An Array of events added when editing a title. 261 * These should all be detached when editing is complete. 262 * 263 * @property edittitleevents 264 * @protected 265 * @type Array 266 * @protected 267 */ 268 edittitleevents: [], 269 270 /** 271 * Initialize the resource toolbox 272 * 273 * For each activity the commands are updated and a reference to the activity is attached. 274 * This way it doesn't matter where the commands are going to called from they have a reference to the 275 * activity that they relate to. 276 * This is essential as some of the actions are displayed in an actionmenu which removes them from the 277 * page flow. 278 * 279 * This function also creates a single event delegate to manage all AJAX actions for all activities on 280 * the page. 281 * 282 * @method initializer 283 * @protected 284 */ 285 initializer: function() { 286 M.course.coursebase.register_module(this); 287 BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this); 288 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this); 289 }, 290 291 /** 292 * Handles the delegation event. When this is fired someone has triggered an action. 293 * 294 * Note not all actions will result in an AJAX enhancement. 295 * 296 * @protected 297 * @method handle_data_action 298 * @param {EventFacade} ev The event that was triggered. 299 * @return {boolean} 300 */ 301 handle_data_action: function(ev) { 302 // We need to get the anchor element that triggered this event. 303 var node = ev.target; 304 if (!node.test('a')) { 305 node = node.ancestor(SELECTOR.ACTIVITYACTION); 306 } 307 308 // From the anchor we can get both the activity (added during initialisation) and the action being 309 // performed (added by the UI as a data attribute). 310 var action = node.getData('action'), 311 activity = node.ancestor(SELECTOR.ACTIVITYLI); 312 313 if (!node.test('a') || !action || !activity) { 314 // It wasn't a valid action node. 315 return; 316 } 317 318 // Switch based upon the action and do the desired thing. 319 switch (action) { 320 case 'edittitle': 321 // The user wishes to edit the title of the event. 322 this.edit_title(ev, node, activity, action); 323 break; 324 case 'moveleft': 325 case 'moveright': 326 // The user changing the indent of the activity. 327 this.change_indent(ev, node, activity, action); 328 break; 329 case 'delete': 330 // The user is deleting the activity. 331 this.delete_with_confirmation(ev, node, activity, action); 332 break; 333 case 'duplicate': 334 // The user is duplicating the activity. 335 this.duplicate(ev, node, activity, action); 336 break; 337 case 'hide': 338 case 'show': 339 // The user is changing the visibility of the activity. 340 this.change_visibility(ev, node, activity, action); 341 break; 342 case 'groupsseparate': 343 case 'groupsvisible': 344 case 'groupsnone': 345 // The user is changing the group mode. 346 callback = 'change_groupmode'; 347 this.change_groupmode(ev, node, activity, action); 348 break; 349 case 'move': 350 case 'update': 351 case 'duplicate': 352 case 'assignroles': 353 break; 354 default: 355 // Nothing to do here! 356 break; 357 } 358 }, 359 360 /** 361 * Add a loading icon to the specified activity. 362 * 363 * The icon is added within the action area. 364 * 365 * @method add_spinner 366 * @param {Node} activity The activity to add a loading icon to 367 * @return {Node|null} The newly created icon, or null if the action area was not found. 368 */ 369 add_spinner: function(activity) { 370 var actionarea = activity.one(SELECTOR.ACTIONAREA); 371 if (actionarea) { 372 return M.util.add_spinner(Y, actionarea); 373 } 374 return null; 375 }, 376 377 /** 378 * Change the indent of the activity or resource. 379 * 380 * @method change_indent 381 * @protected 382 * @param {EventFacade} ev The event that was fired. 383 * @param {Node} button The button that triggered this action. 384 * @param {Node} activity The activity node that this action will be performed on. 385 * @param {String} action The action that has been requested. Will be 'moveleft' or 'moveright'. 386 */ 387 change_indent: function(ev, button, activity, action) { 388 // Prevent the default button action 389 ev.preventDefault(); 390 391 var direction = (action === 'moveleft') ? -1: 1; 392 393 // And we need to determine the current and new indent level 394 var indentdiv = activity.one(SELECTOR.MODINDENTDIV), 395 indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/), 396 oldindent = 0, 397 newindent; 398 399 if (indent) { 400 oldindent = parseInt(indent[1], 10); 401 } 402 newindent = oldindent + parseInt(direction, 10); 403 404 if (newindent < INDENTLIMITS.MIN || newindent > INDENTLIMITS.MAX) { 405 return; 406 } 407 408 if (indent) { 409 indentdiv.removeClass(indent[0]); 410 } 411 412 // Perform the move 413 indentdiv.addClass(CSS.MODINDENTCOUNT + newindent); 414 var data = { 415 'class': 'resource', 416 'field': 'indent', 417 'value': newindent, 418 'id': Y.Moodle.core_course.util.cm.getId(activity) 419 }; 420 var spinner = this.add_spinner(activity); 421 this.send_request(data, spinner); 422 423 var remainingmove; 424 425 // Handle removal/addition of the moveleft button. 426 if (newindent === INDENTLIMITS.MIN) { 427 button.addClass('hidden'); 428 remainingmove = activity.one('.editing_moveright'); 429 } else if (newindent > INDENTLIMITS.MIN && oldindent === INDENTLIMITS.MIN) { 430 button.ancestor('.menu').one('[data-action=moveleft]').removeClass('hidden'); 431 } 432 433 if (newindent === INDENTLIMITS.MAX) { 434 button.addClass('hidden'); 435 remainingmove = activity.one('.editing_moveleft'); 436 } else if (newindent < INDENTLIMITS.MAX && oldindent === INDENTLIMITS.MAX) { 437 button.ancestor('.menu').one('[data-action=moveright]').removeClass('hidden'); 438 } 439 440 // Handle massive indentation to match non-ajax display 441 var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE); 442 if (newindent > 15 && !hashugeclass) { 443 indentdiv.addClass(CSS.MODINDENTHUGE); 444 } else if (newindent <= 15 && hashugeclass) { 445 indentdiv.removeClass(CSS.MODINDENTHUGE); 446 } 447 448 if (ev.type && ev.type === "key" && remainingmove) { 449 remainingmove.focus(); 450 } 451 }, 452 453 /** 454 * Deletes the given activity or resource after confirmation. 455 * 456 * @protected 457 * @method delete_with_confirmation 458 * @param {EventFacade} ev The event that was fired. 459 * @param {Node} button The button that triggered this action. 460 * @param {Node} activity The activity node that this action will be performed on. 461 * @chainable 462 */ 463 delete_with_confirmation: function(ev, button, activity) { 464 // Prevent the default button action 465 ev.preventDefault(); 466 467 // Get the element we're working on 468 var element = activity, 469 // Create confirm string (different if element has or does not have name) 470 confirmstring = '', 471 plugindata = { 472 type: M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1]) 473 }; 474 if (Y.Moodle.core_course.util.cm.getName(element) !== null) { 475 plugindata.name = Y.Moodle.core_course.util.cm.getName(element); 476 confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata); 477 } else { 478 confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata); 479 } 480 481 // Create the confirmation dialogue. 482 var confirm = new M.core.confirm({ 483 question: confirmstring, 484 modal: true 485 }); 486 487 // If it is confirmed. 488 confirm.on('complete-yes', function() { 489 490 // Actually remove the element. 491 element.remove(); 492 var data = { 493 'class': 'resource', 494 'action': 'DELETE', 495 'id': Y.Moodle.core_course.util.cm.getId(element) 496 }; 497 this.send_request(data); 498 if (M.core.actionmenu && M.core.actionmenu.instance) { 499 M.core.actionmenu.instance.hideMenu(); 500 } 501 502 }, this); 503 504 return this; 505 }, 506 507 /** 508 * Duplicates the activity. 509 * 510 * @method duplicate 511 * @protected 512 * @param {EventFacade} ev The event that was fired. 513 * @param {Node} button The button that triggered this action. 514 * @param {Node} activity The activity node that this action will be performed on. 515 * @chainable 516 */ 517 duplicate: function(ev, button, activity) { 518 // Prevent the default button action 519 ev.preventDefault(); 520 521 // Get the element we're working on 522 var element = activity; 523 524 // Add the lightbox. 525 var section = activity.ancestor(M.course.format.get_section_selector(Y)), 526 lightbox = M.util.add_lightbox(Y, section).show(); 527 528 // Build and send the request. 529 var data = { 530 'class': 'resource', 531 'field': 'duplicate', 532 'id': Y.Moodle.core_course.util.cm.getId(element), 533 'sr': button.getData('sr') 534 }; 535 this.send_request(data, lightbox, function(response) { 536 var newcm = Y.Node.create(response.fullcontent); 537 538 // Append to the section? 539 activity.insert(newcm, 'after'); 540 Y.use('moodle-course-coursebase', function() { 541 M.course.coursebase.invoke_function('setup_for_resource', newcm); 542 }); 543 if (M.core.actionmenu && M.core.actionmenu.newDOMNode) { 544 M.core.actionmenu.newDOMNode(newcm); 545 } 546 }); 547 return this; 548 }, 549 550 /** 551 * Changes the visibility of this activity or resource. 552 * 553 * @method change_visibility 554 * @protected 555 * @param {EventFacade} ev The event that was fired. 556 * @param {Node} button The button that triggered this action. 557 * @param {Node} activity The activity node that this action will be performed on. 558 * @param {String} action The action that has been requested. 559 * @chainable 560 */ 561 change_visibility: function(ev, button, activity, action) { 562 // Prevent the default button action 563 ev.preventDefault(); 564 565 // Get the element we're working on 566 var element = activity; 567 var value = this.handle_resource_dim(button, activity, action); 568 569 // Send the request 570 var data = { 571 'class': 'resource', 572 'field': 'visible', 573 'value': value, 574 'id': Y.Moodle.core_course.util.cm.getId(element) 575 }; 576 var spinner = this.add_spinner(element); 577 this.send_request(data, spinner); 578 579 return this; 580 }, 581 582 /** 583 * Handles the UI aspect of dimming the activity or resource. 584 * 585 * @method handle_resource_dim 586 * @protected 587 * @param {Node} button The button that triggered the action. 588 * @param {Node} activity The activity node that this action will be performed on. 589 * @param {String} action 'show' or 'hide'. 590 * @return {Number} 1 if we changed to visible, 0 if we were hiding. 591 */ 592 handle_resource_dim: function(button, activity, action) { 593 var toggleclass = CSS.DIMCLASS, 594 dimarea = activity.one([ 595 SELECTOR.ACTIVITYLINK, 596 SELECTOR.CONTENTWITHOUTLINK 597 ].join(', ')), 598 availabilityinfo = activity.one(CSS.AVAILABILITYINFODIV), 599 nextaction = (action === 'hide') ? 'show': 'hide', 600 buttontext = button.one('span'), 601 newstring = M.util.get_string(nextaction, 'moodle'), 602 buttonimg = button.one('img'); 603 604 // Update button info. 605 buttonimg.setAttrs({ 606 'src': M.util.image_url('t/' + nextaction) 607 }); 608 609 if (Y.Lang.trim(button.getAttribute('title'))) { 610 button.setAttribute('title', newstring); 611 } 612 613 if (Y.Lang.trim(buttonimg.getAttribute('alt'))) { 614 buttonimg.setAttribute('alt', newstring); 615 } 616 617 button.replaceClass('editing_'+action, 'editing_'+nextaction); 618 button.setData('action', nextaction); 619 if (buttontext) { 620 buttontext.set('text', newstring); 621 } 622 623 if (activity.one(SELECTOR.CONTENTWITHOUTLINK)) { 624 dimarea = activity.one(SELECTOR.CONTENTWITHOUTLINK); 625 toggleclass = CSS.DIMMEDTEXT; 626 } 627 628 // If activity is conditionally hidden, then don't toggle. 629 if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) { 630 // Change the UI. 631 dimarea.toggleClass(toggleclass); 632 // We need to toggle dimming on the description too. 633 activity.all(SELECTOR.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT); 634 } 635 // Toggle availablity info for conditional activities. 636 if (availabilityinfo) { 637 availabilityinfo.toggleClass(CSS.HIDE); 638 } 639 return (action === 'hide') ? 0: 1; 640 }, 641 642 /** 643 * Changes the groupmode of the activity to the next groupmode in the sequence. 644 * 645 * @method change_groupmode 646 * @protected 647 * @param {EventFacade} ev The event that was fired. 648 * @param {Node} button The button that triggered this action. 649 * @param {Node} activity The activity node that this action will be performed on. 650 * @chainable 651 */ 652 change_groupmode: function(ev, button, activity) { 653 // Prevent the default button action. 654 ev.preventDefault(); 655 656 // Current Mode 657 var groupmode = parseInt(button.getData('nextgroupmode'), 10), 658 newtitle = '', 659 iconsrc = '', 660 newtitlestr, 661 data, 662 spinner, 663 nextgroupmode = groupmode + 1, 664 buttonimg = button.one('img'); 665 666 if (nextgroupmode > 2) { 667 nextgroupmode = 0; 668 } 669 670 if (groupmode === this.GROUPS_NONE) { 671 newtitle = 'groupsnone'; 672 iconsrc = M.util.image_url('i/groupn', 'moodle'); 673 } else if (groupmode === this.GROUPS_SEPARATE) { 674 newtitle = 'groupsseparate'; 675 iconsrc = M.util.image_url('i/groups', 'moodle'); 676 } else if (groupmode === this.GROUPS_VISIBLE) { 677 newtitle = 'groupsvisible'; 678 iconsrc = M.util.image_url('i/groupv', 'moodle'); 679 } 680 newtitlestr = M.util.get_string('clicktochangeinbrackets', 'moodle', M.util.get_string(newtitle, 'moodle')); 681 682 // Change the UI 683 buttonimg.setAttrs({ 684 'src': iconsrc 685 }); 686 if (Y.Lang.trim(button.getAttribute('title'))) { 687 button.setAttribute('title', newtitlestr).setData('action', newtitle).setData('nextgroupmode', nextgroupmode); 688 } 689 690 if (Y.Lang.trim(buttonimg.getAttribute('alt'))) { 691 buttonimg.setAttribute('alt', newtitlestr); 692 } 693 694 // And send the request 695 data = { 696 'class': 'resource', 697 'field': 'groupmode', 698 'value': groupmode, 699 'id': Y.Moodle.core_course.util.cm.getId(activity) 700 }; 701 702 spinner = this.add_spinner(activity); 703 this.send_request(data, spinner); 704 return this; 705 }, 706 707 /** 708 * Edit the title for the resource 709 * 710 * @method edit_title 711 * @protected 712 * @param {EventFacade} ev The event that was fired. 713 * @param {Node} button The button that triggered this action. 714 * @param {Node} activity The activity node that this action will be performed on. 715 * @param {String} action The action that has been requested. 716 * @chainable 717 */ 718 edit_title: function(ev, button, activity) { 719 // Get the element we're working on 720 var activityid = Y.Moodle.core_course.util.cm.getId(activity), 721 instancename = activity.one(SELECTOR.INSTANCENAME), 722 instance = activity.one(SELECTOR.ACTIVITYINSTANCE), 723 currenttitle = instancename.get('firstChild'), 724 oldtitle = currenttitle.get('data'), 725 titletext = oldtitle, 726 thisevent, 727 anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form. 728 data = { 729 'class': 'resource', 730 'field': 'gettitle', 731 'id': activityid 732 }; 733 734 // Prevent the default actions. 735 ev.preventDefault(); 736 737 this.send_request(data, null, function(response) { 738 if (M.core.actionmenu && M.core.actionmenu.instance) { 739 M.core.actionmenu.instance.hideMenu(); 740 } 741 742 // Try to retrieve the existing string from the server 743 if (response.instancename) { 744 titletext = response.instancename; 745 } 746 747 // Create the editor and submit button 748 var editform = Y.Node.create('<form action="#" />'); 749 var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />') 750 .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle')); 751 var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({ 752 'value': titletext, 753 'autocomplete': 'off', 754 'aria-describedby': 'id_editinstructions', 755 'maxLength': '255' 756 }); 757 758 // Clear the existing content and put the editor in 759 editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode()); 760 editform.appendChild(editor); 761 editform.setData('anchor', anchor); 762 instance.insert(editinstructions, 'before'); 763 anchor.replace(editform); 764 765 // Force the editing instruction to match the mod-indent position. 766 var padside = 'left'; 767 if (right_to_left()) { 768 padside = 'right'; 769 } 770 771 // We hide various components whilst editing: 772 activity.addClass(CSS.EDITINGTITLE); 773 774 // Focus and select the editor text 775 editor.focus().select(); 776 777 // Cancel the edit if we lose focus or the escape key is pressed. 778 thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false); 779 this.edittitleevents.push(thisevent); 780 thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true); 781 this.edittitleevents.push(thisevent); 782 783 // Handle form submission. 784 thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle); 785 this.edittitleevents.push(thisevent); 786 }); 787 return this; 788 }, 789 790 /** 791 * Handles the submit event when editing the activity or resources title. 792 * 793 * @method edit_title_submit 794 * @protected 795 * @param {EventFacade} ev The event that triggered this. 796 * @param {Node} activity The activity whose title we are altering. 797 * @param {String} originaltitle The original title the activity or resource had. 798 */ 799 edit_title_submit: function(ev, activity, originaltitle) { 800 // We don't actually want to submit anything 801 ev.preventDefault(); 802 803 var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value')); 804 this.edit_title_clear(activity); 805 var spinner = this.add_spinner(activity); 806 if (newtitle !== null && newtitle !== "" && newtitle !== originaltitle) { 807 var data = { 808 'class': 'resource', 809 'field': 'updatetitle', 810 'title': newtitle, 811 'id': Y.Moodle.core_course.util.cm.getId(activity) 812 }; 813 this.send_request(data, spinner, function(response) { 814 if (response.instancename) { 815 activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename); 816 } 817 }); 818 } 819 }, 820 821 /** 822 * Handles the cancel event when editing the activity or resources title. 823 * 824 * @method edit_title_cancel 825 * @protected 826 * @param {EventFacade} ev The event that triggered this. 827 * @param {Node} activity The activity whose title we are altering. 828 * @param {Boolean} preventdefault If true we should prevent the default action from occuring. 829 */ 830 edit_title_cancel: function(ev, activity, preventdefault) { 831 if (preventdefault) { 832 ev.preventDefault(); 833 } 834 this.edit_title_clear(activity); 835 }, 836 837 /** 838 * Handles clearing the editing UI and returning things to the original state they were in. 839 * 840 * @method edit_title_clear 841 * @protected 842 * @param {Node} activity The activity whose title we were altering. 843 */ 844 edit_title_clear: function(activity) { 845 // Detach all listen events to prevent duplicate triggers 846 new Y.EventHandle(this.edittitleevents).detach(); 847 848 var editform = activity.one(SELECTOR.ACTIVITYFORM), 849 instructions = activity.one('#id_editinstructions'); 850 if (editform) { 851 editform.replace(editform.getData('anchor')); 852 } 853 if (instructions) { 854 instructions.remove(); 855 } 856 857 // Remove the editing class again to revert the display. 858 activity.removeClass(CSS.EDITINGTITLE); 859 860 // Refocus the link which was clicked originally so the user can continue using keyboard nav. 861 Y.later(100, this, function() { 862 activity.one(SELECTOR.EDITTITLE).focus(); 863 }); 864 }, 865 866 /** 867 * Set the visibility of the specified resource to match the visible parameter. 868 * 869 * Note: This is not a toggle function and only changes the visibility 870 * in the browser (no ajax update is performed). 871 * 872 * @method set_visibility_resource_ui 873 * @param {object} args An object containing the required information to trigger a change. 874 * @param {Node} args.element The resource to toggle 875 * @param {Boolean} args.visible The target visibility 876 */ 877 set_visibility_resource_ui: function(args) { 878 var element = args.element, 879 buttonnode = element.one(SELECTOR.HIDE), 880 // By default we assume that the item is visible and we're going to hide it. 881 currentVisibility = true, 882 targetVisibility = false; 883 884 if (!buttonnode) { 885 // If the buttonnode was not found, try to find the HIDE button 886 // and change the target visibility setting to false. 887 buttonnode = element.one(SELECTOR.SHOW); 888 currentVisibility = false; 889 targetVisibility = true; 890 } 891 892 if (typeof args.visible !== 'undefined') { 893 // If we were provided with a visibility argument, use that instead. 894 targetVisibility = args.visible; 895 } 896 897 // Only trigger a change if necessary. 898 if (currentVisibility !== targetVisibility) { 899 var action = 'hide'; 900 if (targetVisibility) { 901 action = 'show'; 902 } 903 904 this.handle_resource_dim(buttonnode, element, action); 905 } 906 } 907 }, { 908 NAME: 'course-resource-toolbox', 909 ATTRS: { 910 } 911 }); 912 913 M.course.resource_toolbox = null; 914 M.course.init_resource_toolbox = function(config) { 915 M.course.resource_toolbox = new RESOURCETOOLBOX(config); 916 return M.course.resource_toolbox; 917 }; 918 /** 919 * Resource and activity toolbox class. 920 * 921 * This class is responsible for managing AJAX interactions with activities and resources 922 * when viewing a course in editing mode. 923 * 924 * @module moodle-course-toolboxes 925 * @namespace M.course.toolboxes 926 */ 927 928 /** 929 * Section toolbox class. 930 * 931 * This class is responsible for managing AJAX interactions with sections 932 * when viewing a course in editing mode. 933 * 934 * @class section 935 * @constructor 936 * @extends M.course.toolboxes.toolbox 937 */ 938 var SECTIONTOOLBOX = function() { 939 SECTIONTOOLBOX.superclass.constructor.apply(this, arguments); 940 }; 941 942 Y.extend(SECTIONTOOLBOX, TOOLBOX, { 943 /** 944 * Initialize the section toolboxes module. 945 * 946 * Updates all span.commands with relevant handlers and other required changes. 947 * 948 * @method initializer 949 * @protected 950 */ 951 initializer : function() { 952 M.course.coursebase.register_module(this); 953 954 // Section Highlighting. 955 Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this); 956 957 // Section Visibility. 958 Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this); 959 }, 960 961 toggle_hide_section : function(e) { 962 // Prevent the default button action. 963 e.preventDefault(); 964 965 // Get the section we're working on. 966 var section = e.target.ancestor(M.course.format.get_section_selector(Y)), 967 button = e.target.ancestor('a', true), 968 hideicon = button.one('img'), 969 970 // The value to submit 971 value, 972 973 // The text for strings and images. Also determines the icon to display. 974 action, 975 nextaction; 976 977 if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) { 978 section.addClass(CSS.SECTIONHIDDENCLASS); 979 value = 0; 980 action = 'hide'; 981 nextaction = 'show'; 982 } else { 983 section.removeClass(CSS.SECTIONHIDDENCLASS); 984 value = 1; 985 action = 'show'; 986 nextaction = 'hide'; 987 } 988 989 var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format')); 990 hideicon.setAttrs({ 991 'alt' : newstring, 992 'src' : M.util.image_url('i/' + nextaction) 993 }); 994 button.set('title', newstring); 995 996 // Change the highlight status 997 var data = { 998 'class' : 'section', 999 'field' : 'visible', 1000 'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true)), 1001 'value' : value 1002 }; 1003 1004 var lightbox = M.util.add_lightbox(Y, section); 1005 lightbox.show(); 1006 1007 this.send_request(data, lightbox, function(response) { 1008 var activities = section.all(SELECTOR.ACTIVITYLI); 1009 activities.each(function(node) { 1010 var button; 1011 if (node.one(SELECTOR.SHOW)) { 1012 button = node.one(SELECTOR.SHOW); 1013 } else { 1014 button = node.one(SELECTOR.HIDE); 1015 } 1016 var activityid = Y.Moodle.core_course.util.cm.getId(node); 1017 1018 // NOTE: resourcestotoggle is returned as a string instead 1019 // of a Number so we must cast our activityid to a String. 1020 if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) { 1021 M.course.resource_toolbox.handle_resource_dim(button, node, action); 1022 } 1023 }, this); 1024 }); 1025 }, 1026 1027 /** 1028 * Toggle highlighting the current section. 1029 * 1030 * @method toggle_highlight 1031 * @param {EventFacade} e 1032 */ 1033 toggle_highlight : function(e) { 1034 // Prevent the default button action. 1035 e.preventDefault(); 1036 1037 // Get the section we're working on. 1038 var section = e.target.ancestor(M.course.format.get_section_selector(Y)); 1039 var button = e.target.ancestor('a', true); 1040 var buttonicon = button.one('img'); 1041 1042 // Determine whether the marker is currently set. 1043 var togglestatus = section.hasClass('current'); 1044 var value = 0; 1045 1046 // Set the current highlighted item text. 1047 var old_string = M.util.get_string('markthistopic', 'moodle'); 1048 Y.one(SELECTOR.PAGECONTENT) 1049 .all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT) 1050 .set('title', old_string); 1051 Y.one(SELECTOR.PAGECONTENT) 1052 .all(M.course.format.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img') 1053 .set('alt', old_string) 1054 .set('src', M.util.image_url('i/marker')); 1055 1056 // Remove the highlighting from all sections. 1057 Y.one(SELECTOR.PAGECONTENT).all(M.course.format.get_section_selector(Y)) 1058 .removeClass('current'); 1059 1060 // Then add it if required to the selected section. 1061 if (!togglestatus) { 1062 section.addClass('current'); 1063 value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.course.format.get_section_wrapper(Y), true)); 1064 var new_string = M.util.get_string('markedthistopic', 'moodle'); 1065 button 1066 .set('title', new_string); 1067 buttonicon 1068 .set('alt', new_string) 1069 .set('src', M.util.image_url('i/marked')); 1070 } 1071 1072 // Change the highlight status. 1073 var data = { 1074 'class' : 'course', 1075 'field' : 'marker', 1076 'value' : value 1077 }; 1078 var lightbox = M.util.add_lightbox(Y, section); 1079 lightbox.show(); 1080 this.send_request(data, lightbox); 1081 } 1082 }, { 1083 NAME : 'course-section-toolbox', 1084 ATTRS : { 1085 } 1086 }); 1087 1088 M.course.init_section_toolbox = function(config) { 1089 return new SECTIONTOOLBOX(config); 1090 }; 1091 1092 1093 }, '@VERSION@', {"requires": ["node", "base", "event-key", "node", "io", "moodle-course-coursebase", "moodle-course-util"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |