[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) { 2 3 /* eslint-disable no-unused-vars */ 4 /** 5 * Resource and activity toolbox class. 6 * 7 * This class is responsible for managing AJAX interactions with activities and resources 8 * when viewing a course in editing mode. 9 * 10 * @module moodle-course-toolboxes 11 * @namespace M.course.toolboxes 12 */ 13 14 // The CSS classes we use. 15 var CSS = { 16 ACTIVITYINSTANCE: 'activityinstance', 17 AVAILABILITYINFODIV: 'div.availabilityinfo', 18 CONTENTWITHOUTLINK: 'contentwithoutlink', 19 CONDITIONALHIDDEN: 'conditionalhidden', 20 DIMCLASS: 'dimmed', 21 DIMMEDTEXT: 'dimmed_text', 22 EDITINSTRUCTIONS: 'editinstructions', 23 EDITINGMAXMARK: 'editor_displayed', 24 HIDE: 'hide', 25 JOIN: 'page_join', 26 MODINDENTCOUNT: 'mod-indent-', 27 MODINDENTHUGE: 'mod-indent-huge', 28 PAGE: 'page', 29 SECTIONHIDDENCLASS: 'hidden', 30 SECTIONIDPREFIX: 'section-', 31 SLOT: 'slot', 32 SHOW: 'editing_show', 33 TITLEEDITOR: 'titleeditor' 34 }, 35 // The CSS selectors we use. 36 SELECTOR = { 37 ACTIONAREA: '.actions', 38 ACTIONLINKTEXT: '.actionlinktext', 39 ACTIVITYACTION: 'a.cm-edit-action[data-action], a.editing_maxmark, a.editing_section, input.shuffle_questions', 40 ACTIVITYFORM: 'span.instancemaxmarkcontainer form', 41 ACTIVITYINSTANCE: '.' + CSS.ACTIVITYINSTANCE, 42 SECTIONINSTANCE: '.sectioninstance', 43 ACTIVITYLI: 'li.activity, li.section', 44 ACTIVITYMAXMARK: 'input[name=maxmark]', 45 COMMANDSPAN: '.commands', 46 CONTENTAFTERLINK: 'div.contentafterlink', 47 CONTENTWITHOUTLINK: 'div.contentwithoutlink', 48 DELETESECTIONICON: 'a.editing_delete img', 49 EDITMAXMARK: 'a.editing_maxmark', 50 EDITSECTION: 'a.editing_section', 51 EDITSECTIONICON: 'a.editing_section img', 52 EDITSHUFFLEQUESTIONSACTION: 'input.cm-edit-action[data-action]', 53 EDITSHUFFLEAREA: '.instanceshufflequestions .shuffle-progress', 54 HIDE: 'a.editing_hide', 55 HIGHLIGHT: 'a.editing_highlight', 56 INSTANCENAME: 'span.instancename', 57 INSTANCEMAXMARK: 'span.instancemaxmark', 58 INSTANCESECTION: 'span.instancesection', 59 INSTANCESECTIONAREA: 'div.section-heading', 60 MODINDENTDIV: '.mod-indent', 61 MODINDENTOUTER: '.mod-indent-outer', 62 NUMQUESTIONS: '.numberofquestions', 63 PAGECONTENT: 'div#page-content', 64 PAGELI: 'li.page', 65 SECTIONUL: 'ul.section', 66 SECTIONFORM: '.instancesectioncontainer form', 67 SECTIONINPUT: 'input[name=section]', 68 SHOW: 'a.' + CSS.SHOW, 69 SLOTLI: 'li.slot', 70 SUMMARKS: '.mod_quiz_summarks' 71 }, 72 BODY = Y.one(document.body); 73 74 // Setup the basic namespace. 75 M.mod_quiz = M.mod_quiz || {}; 76 77 /** 78 * The toolbox class is a generic class which should never be directly 79 * instantiated. Please extend it instead. 80 * 81 * @class toolbox 82 * @constructor 83 * @protected 84 * @extends Base 85 */ 86 var TOOLBOX = function() { 87 TOOLBOX.superclass.constructor.apply(this, arguments); 88 }; 89 90 Y.extend(TOOLBOX, Y.Base, { 91 /** 92 * Send a request using the REST API 93 * 94 * @method send_request 95 * @param {Object} data The data to submit with the AJAX request 96 * @param {Node} [statusspinner] A statusspinner which may contain a section loader 97 * @param {Function} success_callback The callback to use on success 98 * @param {Object} [optionalconfig] Any additional configuration to submit 99 * @chainable 100 */ 101 send_request: function(data, statusspinner, success_callback, optionalconfig) { 102 // Default data structure 103 if (!data) { 104 data = {}; 105 } 106 // Handle any variables which we must pass back through to 107 var pageparams = this.get('config').pageparams, 108 varname; 109 for (varname in pageparams) { 110 data[varname] = pageparams[varname]; 111 } 112 113 data.sesskey = M.cfg.sesskey; 114 data.courseid = this.get('courseid'); 115 data.quizid = this.get('quizid'); 116 117 var uri = M.cfg.wwwroot + this.get('ajaxurl'); 118 119 // Define the configuration to send with the request 120 var responsetext = []; 121 var config = { 122 method: 'POST', 123 data: data, 124 on: { 125 success: function(tid, response) { 126 try { 127 responsetext = Y.JSON.parse(response.responseText); 128 if (responsetext.error) { 129 new M.core.ajaxException(responsetext); 130 } 131 } catch (e) { 132 // Ignore. 133 } 134 135 // Run the callback if we have one. 136 if (responsetext.hasOwnProperty('newsummarks')) { 137 Y.one(SELECTOR.SUMMARKS).setHTML(responsetext.newsummarks); 138 } 139 if (responsetext.hasOwnProperty('newnumquestions')) { 140 Y.one(SELECTOR.NUMQUESTIONS).setHTML( 141 M.util.get_string('numquestionsx', 'quiz', responsetext.newnumquestions) 142 ); 143 } 144 if (success_callback) { 145 Y.bind(success_callback, this, responsetext)(); 146 } 147 148 if (statusspinner) { 149 window.setTimeout(function() { 150 statusspinner.hide(); 151 }, 400); 152 } 153 }, 154 failure: function(tid, response) { 155 if (statusspinner) { 156 statusspinner.hide(); 157 } 158 new M.core.ajaxException(response); 159 } 160 }, 161 context: this 162 }; 163 164 // Apply optional config 165 if (optionalconfig) { 166 for (varname in optionalconfig) { 167 config[varname] = optionalconfig[varname]; 168 } 169 } 170 171 if (statusspinner) { 172 statusspinner.show(); 173 } 174 175 // Send the request 176 Y.io(uri, config); 177 return this; 178 } 179 }, 180 { 181 NAME: 'mod_quiz-toolbox', 182 ATTRS: { 183 /** 184 * The ID of the Moodle Course being edited. 185 * 186 * @attribute courseid 187 * @default 0 188 * @type Number 189 */ 190 courseid: { 191 'value': 0 192 }, 193 194 /** 195 * The Moodle course format. 196 * 197 * @attribute format 198 * @default 'topics' 199 * @type String 200 */ 201 quizid: { 202 'value': 0 203 }, 204 /** 205 * The URL to use when submitting requests. 206 * @attribute ajaxurl 207 * @default null 208 * @type String 209 */ 210 ajaxurl: { 211 'value': null 212 }, 213 /** 214 * Any additional configuration passed when creating the instance. 215 * 216 * @attribute config 217 * @default {} 218 * @type Object 219 */ 220 config: { 221 'value': {} 222 } 223 } 224 } 225 ); 226 /* global TOOLBOX, BODY, SELECTOR */ 227 228 /** 229 * Resource and activity toolbox class. 230 * 231 * This class is responsible for managing AJAX interactions with activities and resources 232 * when viewing a quiz in editing mode. 233 * 234 * @module mod_quiz-resource-toolbox 235 * @namespace M.mod_quiz.resource_toolbox 236 */ 237 238 /** 239 * Resource and activity toolbox class. 240 * 241 * This is a class extending TOOLBOX containing code specific to resources 242 * 243 * This class is responsible for managing AJAX interactions with activities and resources 244 * when viewing a quiz in editing mode. 245 * 246 * @class resources 247 * @constructor 248 * @extends M.course.toolboxes.toolbox 249 */ 250 var RESOURCETOOLBOX = function() { 251 RESOURCETOOLBOX.superclass.constructor.apply(this, arguments); 252 }; 253 254 Y.extend(RESOURCETOOLBOX, TOOLBOX, { 255 /** 256 * An Array of events added when editing a max mark field. 257 * These should all be detached when editing is complete. 258 * 259 * @property editmaxmarkevents 260 * @protected 261 * @type Array 262 * @protected 263 */ 264 editmaxmarkevents: [], 265 266 /** 267 * 268 */ 269 NODE_PAGE: 1, 270 NODE_SLOT: 2, 271 NODE_JOIN: 3, 272 273 /** 274 * Initialize the resource toolbox 275 * 276 * For each activity the commands are updated and a reference to the activity is attached. 277 * This way it doesn't matter where the commands are going to called from they have a reference to the 278 * activity that they relate to. 279 * This is essential as some of the actions are displayed in an actionmenu which removes them from the 280 * page flow. 281 * 282 * This function also creates a single event delegate to manage all AJAX actions for all activities on 283 * the page. 284 * 285 * @method initializer 286 * @protected 287 */ 288 initializer: function() { 289 M.mod_quiz.quizbase.register_module(this); 290 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this); 291 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.DEPENDENCY_LINK, this); 292 }, 293 294 /** 295 * Handles the delegation event. When this is fired someone has triggered an action. 296 * 297 * Note not all actions will result in an AJAX enhancement. 298 * 299 * @protected 300 * @method handle_data_action 301 * @param {EventFacade} ev The event that was triggered. 302 * @returns {boolean} 303 */ 304 handle_data_action: function(ev) { 305 // We need to get the anchor element that triggered this event. 306 var node = ev.target; 307 if (!node.test('a')) { 308 node = node.ancestor(SELECTOR.ACTIVITYACTION); 309 } 310 311 // From the anchor we can get both the activity (added during initialisation) and the action being 312 // performed (added by the UI as a data attribute). 313 var action = node.getData('action'), 314 activity = node.ancestor(SELECTOR.ACTIVITYLI); 315 316 if (!node.test('a') || !action || !activity) { 317 // It wasn't a valid action node. 318 return; 319 } 320 321 // Switch based upon the action and do the desired thing. 322 switch (action) { 323 case 'editmaxmark': 324 // The user wishes to edit the maxmark of the resource. 325 this.edit_maxmark(ev, node, activity, action); 326 break; 327 case 'delete': 328 // The user is deleting the activity. 329 this.delete_with_confirmation(ev, node, activity, action); 330 break; 331 case 'addpagebreak': 332 case 'removepagebreak': 333 // The user is adding or removing a page break. 334 this.update_page_break(ev, node, activity, action); 335 break; 336 case 'adddependency': 337 case 'removedependency': 338 // The user is adding or removing a dependency between questions. 339 this.update_dependency(ev, node, activity, action); 340 break; 341 default: 342 // Nothing to do here! 343 break; 344 } 345 }, 346 347 /** 348 * Add a loading icon to the specified activity. 349 * 350 * The icon is added within the action area. 351 * 352 * @method add_spinner 353 * @param {Node} activity The activity to add a loading icon to 354 * @return {Node|null} The newly created icon, or null if the action area was not found. 355 */ 356 add_spinner: function(activity) { 357 var actionarea = activity.one(SELECTOR.ACTIONAREA); 358 if (actionarea) { 359 return M.util.add_spinner(Y, actionarea); 360 } 361 return null; 362 }, 363 364 /** 365 * Deletes the given activity or resource after confirmation. 366 * 367 * @protected 368 * @method delete_with_confirmation 369 * @param {EventFacade} ev The event that was fired. 370 * @param {Node} button The button that triggered this action. 371 * @param {Node} activity The activity node that this action will be performed on. 372 * @chainable 373 */ 374 delete_with_confirmation: function(ev, button, activity) { 375 // Prevent the default button action. 376 ev.preventDefault(); 377 378 // Get the element we're working on. 379 var element = activity, 380 // Create confirm string (different if element has or does not have name) 381 confirmstring = '', 382 qtypename = M.util.get_string('pluginname', 383 'qtype_' + element.getAttribute('class').match(/qtype_([^\s]*)/)[1]); 384 confirmstring = M.util.get_string('confirmremovequestion', 'quiz', qtypename); 385 386 // Create the confirmation dialogue. 387 var confirm = new M.core.confirm({ 388 question: confirmstring, 389 modal: true 390 }); 391 392 // If it is confirmed. 393 confirm.on('complete-yes', function() { 394 395 var spinner = this.add_spinner(element); 396 var data = { 397 'class': 'resource', 398 'action': 'DELETE', 399 'id': Y.Moodle.mod_quiz.util.slot.getId(element) 400 }; 401 this.send_request(data, spinner, function(response) { 402 if (response.deleted) { 403 // Actually remove the element. 404 Y.Moodle.mod_quiz.util.slot.remove(element); 405 this.reorganise_edit_page(); 406 if (M.core.actionmenu && M.core.actionmenu.instance) { 407 M.core.actionmenu.instance.hideMenu(ev); 408 } 409 } 410 }); 411 412 }, this); 413 414 return this; 415 }, 416 417 418 /** 419 * Edit the maxmark for the resource 420 * 421 * @protected 422 * @method edit_maxmark 423 * @param {EventFacade} ev The event that was fired. 424 * @param {Node} button The button that triggered this action. 425 * @param {Node} activity The activity node that this action will be performed on. 426 * @param {String} action The action that has been requested. 427 * @return Boolean 428 */ 429 edit_maxmark: function(ev, button, activity) { 430 // Get the element we're working on 431 var instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK), 432 instance = activity.one(SELECTOR.ACTIVITYINSTANCE), 433 currentmaxmark = instancemaxmark.get('firstChild'), 434 oldmaxmark = currentmaxmark.get('data'), 435 maxmarktext = oldmaxmark, 436 thisevent, 437 anchor = instancemaxmark, // Grab the anchor so that we can swap it with the edit form. 438 data = { 439 'class': 'resource', 440 'field': 'getmaxmark', 441 'id': Y.Moodle.mod_quiz.util.slot.getId(activity) 442 }; 443 444 // Prevent the default actions. 445 ev.preventDefault(); 446 447 this.send_request(data, null, function(response) { 448 if (M.core.actionmenu && M.core.actionmenu.instance) { 449 M.core.actionmenu.instance.hideMenu(ev); 450 } 451 452 // Try to retrieve the existing string from the server. 453 if (response.instancemaxmark) { 454 maxmarktext = response.instancemaxmark; 455 } 456 457 // Create the editor and submit button. 458 var editform = Y.Node.create('<form action="#" />'); 459 var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />') 460 .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle')); 461 var editor = Y.Node.create('<input name="maxmark" type="text" class="' + CSS.TITLEEDITOR + '" />').setAttrs({ 462 'value': maxmarktext, 463 'autocomplete': 'off', 464 'aria-describedby': 'id_editinstructions', 465 'maxLength': '12', 466 'size': parseInt(this.get('config').questiondecimalpoints, 10) + 2 467 }); 468 469 // Clear the existing content and put the editor in. 470 editform.appendChild(editor); 471 editform.setData('anchor', anchor); 472 instance.insert(editinstructions, 'before'); 473 anchor.replace(editform); 474 475 // We hide various components whilst editing: 476 activity.addClass(CSS.EDITINGMAXMARK); 477 478 // Focus and select the editor text. 479 editor.focus().select(); 480 481 // Cancel the edit if we lose focus or the escape key is pressed. 482 thisevent = editor.on('blur', this.edit_maxmark_cancel, this, activity, false); 483 this.editmaxmarkevents.push(thisevent); 484 thisevent = editor.on('key', this.edit_maxmark_cancel, 'esc', this, activity, true); 485 this.editmaxmarkevents.push(thisevent); 486 487 // Handle form submission. 488 thisevent = editform.on('submit', this.edit_maxmark_submit, this, activity, oldmaxmark); 489 this.editmaxmarkevents.push(thisevent); 490 }); 491 }, 492 493 /** 494 * Handles the submit event when editing the activity or resources maxmark. 495 * 496 * @protected 497 * @method edit_maxmark_submit 498 * @param {EventFacade} ev The event that triggered this. 499 * @param {Node} activity The activity whose maxmark we are altering. 500 * @param {String} originalmaxmark The original maxmark the activity or resource had. 501 */ 502 edit_maxmark_submit: function(ev, activity, originalmaxmark) { 503 // We don't actually want to submit anything. 504 ev.preventDefault(); 505 var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value')); 506 var spinner = this.add_spinner(activity); 507 this.edit_maxmark_clear(activity); 508 activity.one(SELECTOR.INSTANCEMAXMARK).setContent(newmaxmark); 509 if (newmaxmark !== null && newmaxmark !== "" && newmaxmark !== originalmaxmark) { 510 var data = { 511 'class': 'resource', 512 'field': 'updatemaxmark', 513 'maxmark': newmaxmark, 514 'id': Y.Moodle.mod_quiz.util.slot.getId(activity) 515 }; 516 this.send_request(data, spinner, function(response) { 517 if (response.instancemaxmark) { 518 activity.one(SELECTOR.INSTANCEMAXMARK).setContent(response.instancemaxmark); 519 } 520 }); 521 } 522 }, 523 524 /** 525 * Handles the cancel event when editing the activity or resources maxmark. 526 * 527 * @protected 528 * @method edit_maxmark_cancel 529 * @param {EventFacade} ev The event that triggered this. 530 * @param {Node} activity The activity whose maxmark we are altering. 531 * @param {Boolean} preventdefault If true we should prevent the default action from occuring. 532 */ 533 edit_maxmark_cancel: function(ev, activity, preventdefault) { 534 if (preventdefault) { 535 ev.preventDefault(); 536 } 537 this.edit_maxmark_clear(activity); 538 }, 539 540 /** 541 * Handles clearing the editing UI and returning things to the original state they were in. 542 * 543 * @protected 544 * @method edit_maxmark_clear 545 * @param {Node} activity The activity whose maxmark we were altering. 546 */ 547 edit_maxmark_clear: function(activity) { 548 // Detach all listen events to prevent duplicate triggers 549 new Y.EventHandle(this.editmaxmarkevents).detach(); 550 551 var editform = activity.one(SELECTOR.ACTIVITYFORM), 552 instructions = activity.one('#id_editinstructions'); 553 if (editform) { 554 editform.replace(editform.getData('anchor')); 555 } 556 if (instructions) { 557 instructions.remove(); 558 } 559 560 // Remove the editing class again to revert the display. 561 activity.removeClass(CSS.EDITINGMAXMARK); 562 563 // Refocus the link which was clicked originally so the user can continue using keyboard nav. 564 Y.later(100, this, function() { 565 activity.one(SELECTOR.EDITMAXMARK).focus(); 566 }); 567 568 // TODO MDL-50768 This hack is to keep Behat happy until they release a version of 569 // MinkSelenium2Driver that fixes 570 // https://github.com/Behat/MinkSelenium2Driver/issues/80. 571 if (!Y.one('input[name=maxmark')) { 572 Y.one('body').append('<input type="text" name="maxmark" style="display: none">'); 573 } 574 }, 575 576 /** 577 * Joins or separates the given slot with the page of the previous slot. Reorders the pages of 578 * the other slots 579 * 580 * @protected 581 * @method update_page_break 582 * @param {EventFacade} ev The event that was fired. 583 * @param {Node} button The button that triggered this action. 584 * @param {Node} activity The activity node that this action will be performed on. 585 * @param {String} action The action, addpagebreak or removepagebreak. 586 * @chainable 587 */ 588 update_page_break: function(ev, button, activity, action) { 589 // Prevent the default button action 590 ev.preventDefault(); 591 592 var nextactivity = activity.next('li.activity.slot'); 593 var spinner = this.add_spinner(nextactivity); 594 var value = action === 'removepagebreak' ? 1 : 2; 595 596 var data = { 597 'class': 'resource', 598 'field': 'updatepagebreak', 599 'id': Y.Moodle.mod_quiz.util.slot.getId(nextactivity), 600 'value': value 601 }; 602 603 this.send_request(data, spinner, function(response) { 604 if (response.slots) { 605 if (action === 'addpagebreak') { 606 Y.Moodle.mod_quiz.util.page.add(activity); 607 } else { 608 var page = activity.next(Y.Moodle.mod_quiz.util.page.SELECTORS.PAGE); 609 Y.Moodle.mod_quiz.util.page.remove(page, true); 610 } 611 this.reorganise_edit_page(); 612 } 613 }); 614 615 return this; 616 }, 617 618 /** 619 * Updates a slot to either require the question in the previous slot to 620 * have been answered, or not, 621 * 622 * @protected 623 * @method update_page_break 624 * @param {EventFacade} ev The event that was fired. 625 * @param {Node} button The button that triggered this action. 626 * @param {Node} activity The activity node that this action will be performed on. 627 * @param {String} action The action, adddependency or removedependency. 628 * @chainable 629 */ 630 update_dependency: function(ev, button, activity, action) { 631 // Prevent the default button action. 632 ev.preventDefault(); 633 var spinner = this.add_spinner(activity); 634 635 var data = { 636 'class': 'resource', 637 'field': 'updatedependency', 638 'id': Y.Moodle.mod_quiz.util.slot.getId(activity), 639 'value': action === 'adddependency' ? 1 : 0 640 }; 641 642 this.send_request(data, spinner, function(response) { 643 if (response.hasOwnProperty('requireprevious')) { 644 Y.Moodle.mod_quiz.util.slot.updateDependencyIcon(activity, response.requireprevious); 645 } 646 }); 647 648 return this; 649 }, 650 651 /** 652 * Reorganise the UI after every edit action. 653 * 654 * @protected 655 * @method reorganise_edit_page 656 */ 657 reorganise_edit_page: function() { 658 Y.Moodle.mod_quiz.util.slot.reorderSlots(); 659 Y.Moodle.mod_quiz.util.slot.reorderPageBreaks(); 660 Y.Moodle.mod_quiz.util.page.reorderPages(); 661 Y.Moodle.mod_quiz.util.slot.updateOneSlotSections(); 662 Y.Moodle.mod_quiz.util.slot.updateAllDependencyIcons(); 663 }, 664 665 NAME: 'mod_quiz-resource-toolbox', 666 ATTRS: { 667 courseid: { 668 'value': 0 669 }, 670 quizid: { 671 'value': 0 672 } 673 } 674 }); 675 676 M.mod_quiz.resource_toolbox = null; 677 M.mod_quiz.init_resource_toolbox = function(config) { 678 M.mod_quiz.resource_toolbox = new RESOURCETOOLBOX(config); 679 return M.mod_quiz.resource_toolbox; 680 }; 681 /* global TOOLBOX, BODY, SELECTOR */ 682 683 /** 684 * Section toolbox class. 685 * 686 * This class is responsible for managing AJAX interactions with sections 687 * when adding, editing, removing section headings. 688 * 689 * @module moodle-mod_quiz-toolboxes 690 * @namespace M.mod_quiz.toolboxes 691 */ 692 693 /** 694 * Section toolbox class. 695 * 696 * This class is responsible for managing AJAX interactions with sections 697 * when adding, editing, removing section headings when editing a quiz. 698 * 699 * @class section 700 * @constructor 701 * @extends M.mod_quiz.toolboxes.toolbox 702 */ 703 var SECTIONTOOLBOX = function() { 704 SECTIONTOOLBOX.superclass.constructor.apply(this, arguments); 705 }; 706 707 Y.extend(SECTIONTOOLBOX, TOOLBOX, { 708 /** 709 * An Array of events added when editing a max mark field. 710 * These should all be detached when editing is complete. 711 * 712 * @property editsectionevents 713 * @protected 714 * @type Array 715 * @protected 716 */ 717 editsectionevents: [], 718 719 /** 720 * Initialize the section toolboxes module. 721 * 722 * Updates all span.commands with relevant handlers and other required changes. 723 * 724 * @method initializer 725 * @protected 726 */ 727 initializer: function() { 728 M.mod_quiz.quizbase.register_module(this); 729 730 BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this); 731 Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this); 732 Y.delegate('change', this.handle_data_action, BODY, SELECTOR.EDITSHUFFLEQUESTIONSACTION, this); 733 }, 734 735 /** 736 * Handles the delegation event. When this is fired someone has triggered an action. 737 * 738 * Note not all actions will result in an AJAX enhancement. 739 * 740 * @protected 741 * @method handle_data_action 742 * @param {EventFacade} ev The event that was triggered. 743 * @returns {boolean} 744 */ 745 handle_data_action: function(ev) { 746 // We need to get the anchor element that triggered this event. 747 var node = ev.target; 748 if (!node.test('a') && !node.test('input[data-action]')) { 749 node = node.ancestor(SELECTOR.ACTIVITYACTION); 750 } 751 752 // From the anchor we can get both the activity (added during initialisation) and the action being 753 // performed (added by the UI as a data attribute). 754 var action = node.getData('action'), 755 activity = node.ancestor(SELECTOR.ACTIVITYLI); 756 757 if ((!node.test('a') && !node.test('input[data-action]')) || !action || !activity) { 758 // It wasn't a valid action node. 759 return; 760 } 761 762 // Switch based upon the action and do the desired thing. 763 switch (action) { 764 case 'edit_section_title': 765 // The user wishes to edit the section headings. 766 this.edit_section_title(ev, node, activity, action); 767 break; 768 case 'shuffle_questions': 769 // The user wishes to edit the shuffle questions of the section (resource). 770 this.edit_shuffle_questions(ev, node, activity, action); 771 break; 772 case 'deletesection': 773 // The user is deleting the activity. 774 this.delete_section_with_confirmation(ev, node, activity, action); 775 break; 776 default: 777 // Nothing to do here! 778 break; 779 } 780 }, 781 782 /** 783 * Deletes the given section heading after confirmation. 784 * 785 * @protected 786 * @method delete_section_with_confirmation 787 * @param {EventFacade} ev The event that was fired. 788 * @param {Node} button The button that triggered this action. 789 * @param {Node} activity The activity node that this action will be performed on. 790 * @chainable 791 */ 792 delete_section_with_confirmation: function(ev, button, activity) { 793 // Prevent the default button action. 794 ev.preventDefault(); 795 796 // Create the confirmation dialogue. 797 var confirm = new M.core.confirm({ 798 question: M.util.get_string('confirmremovesectionheading', 'quiz', activity.get('aria-label')), 799 modal: true 800 }); 801 802 // If it is confirmed. 803 confirm.on('complete-yes', function() { 804 805 var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.ACTIONAREA)); 806 var data = { 807 'class': 'section', 808 'action': 'DELETE', 809 'id': activity.get('id').replace('section-', '') 810 }; 811 this.send_request(data, spinner, function(response) { 812 if (response.deleted) { 813 window.location.reload(true); 814 } 815 }); 816 817 }, this); 818 }, 819 820 /** 821 * Edit the edit section title for the section 822 * 823 * @protected 824 * @method edit_section_title 825 * @param {EventFacade} ev The event that was fired. 826 * @param {Node} button The button that triggered this action. 827 * @param {Node} activity The activity node that this action will be performed on. 828 * @param {String} action The action that has been requested. 829 * @return Boolean 830 */ 831 edit_section_title: function(ev, button, activity) { 832 // Get the element we're working on 833 var activityid = activity.get('id').replace('section-', ''), 834 instancesection = activity.one(SELECTOR.INSTANCESECTION), 835 thisevent, 836 anchor = instancesection, // Grab the anchor so that we can swap it with the edit form. 837 data = { 838 'class': 'section', 839 'field': 'getsectiontitle', 840 'id': activityid 841 }; 842 843 // Prevent the default actions. 844 ev.preventDefault(); 845 846 this.send_request(data, null, function(response) { 847 // Try to retrieve the existing string from the server. 848 var oldtext = response.instancesection; 849 850 // Create the editor and submit button. 851 var editform = Y.Node.create('<form action="#" />'); 852 var editinstructions = Y.Node.create('<span class="' + CSS.EDITINSTRUCTIONS + '" id="id_editinstructions" />') 853 .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle')); 854 var editor = Y.Node.create('<input name="section" type="text" />').setAttrs({ 855 'value': oldtext, 856 'autocomplete': 'off', 857 'aria-describedby': 'id_editinstructions', 858 'maxLength': '255' // This is the maxlength in DB. 859 }); 860 861 // Clear the existing content and put the editor in. 862 editform.appendChild(editor); 863 editform.setData('anchor', anchor); 864 instancesection.insert(editinstructions, 'before'); 865 anchor.replace(editform); 866 867 // Focus and select the editor text. 868 editor.focus().select(); 869 // Cancel the edit if we lose focus or the escape key is pressed. 870 thisevent = editor.on('blur', this.edit_section_title_cancel, this, activity, false); 871 this.editsectionevents.push(thisevent); 872 thisevent = editor.on('key', this.edit_section_title_cancel, 'esc', this, activity, true); 873 this.editsectionevents.push(thisevent); 874 // Handle form submission. 875 thisevent = editform.on('submit', this.edit_section_title_submit, this, activity, oldtext); 876 this.editsectionevents.push(thisevent); 877 }); 878 }, 879 880 /** 881 * Handles the submit event when editing section heading. 882 * 883 * @protected 884 * @method edit_section_title_submiy 885 * @param {EventFacade} ev The event that triggered this. 886 * @param {Node} activity The activity whose maxmark we are altering. 887 * @param {String} oldtext The original maxmark the activity or resource had. 888 */ 889 edit_section_title_submit: function(ev, activity, oldtext) { 890 // We don't actually want to submit anything. 891 ev.preventDefault(); 892 var newtext = Y.Lang.trim(activity.one(SELECTOR.SECTIONFORM + ' ' + SELECTOR.SECTIONINPUT).get('value')); 893 var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.INSTANCESECTIONAREA)); 894 this.edit_section_title_clear(activity); 895 if (newtext !== null && newtext !== oldtext) { 896 activity.one(SELECTOR.INSTANCESECTION).setContent(newtext); 897 var data = { 898 'class': 'section', 899 'field': 'updatesectiontitle', 900 'newheading': newtext, 901 'id': activity.get('id').replace('section-', '') 902 }; 903 this.send_request(data, spinner, function(response) { 904 if (response) { 905 activity.one(SELECTOR.INSTANCESECTION).setContent(response.instancesection); 906 activity.one(SELECTOR.EDITSECTIONICON).set('title', 907 M.util.get_string('sectionheadingedit', 'quiz', response.instancesection)); 908 activity.one(SELECTOR.EDITSECTIONICON).set('alt', 909 M.util.get_string('sectionheadingedit', 'quiz', response.instancesection)); 910 var deleteicon = activity.one(SELECTOR.DELETESECTIONICON); 911 if (deleteicon) { 912 deleteicon.set('title', M.util.get_string('sectionheadingremove', 'quiz', response.instancesection)); 913 deleteicon.set('alt', M.util.get_string('sectionheadingremove', 'quiz', response.instancesection)); 914 } 915 } 916 }); 917 } 918 }, 919 920 /** 921 * Handles the cancel event when editing the activity or resources maxmark. 922 * 923 * @protected 924 * @method edit_maxmark_cancel 925 * @param {EventFacade} ev The event that triggered this. 926 * @param {Node} activity The activity whose maxmark we are altering. 927 * @param {Boolean} preventdefault If true we should prevent the default action from occuring. 928 */ 929 edit_section_title_cancel: function(ev, activity, preventdefault) { 930 if (preventdefault) { 931 ev.preventDefault(); 932 } 933 this.edit_section_title_clear(activity); 934 }, 935 936 /** 937 * Handles clearing the editing UI and returning things to the original state they were in. 938 * 939 * @protected 940 * @method edit_maxmark_clear 941 * @param {Node} activity The activity whose maxmark we were altering. 942 */ 943 edit_section_title_clear: function(activity) { 944 // Detach all listen events to prevent duplicate triggers 945 new Y.EventHandle(this.editsectionevents).detach(); 946 947 var editform = activity.one(SELECTOR.SECTIONFORM), 948 instructions = activity.one('#id_editinstructions'); 949 if (editform) { 950 editform.replace(editform.getData('anchor')); 951 } 952 if (instructions) { 953 instructions.remove(); 954 } 955 956 // Refocus the link which was clicked originally so the user can continue using keyboard nav. 957 Y.later(100, this, function() { 958 activity.one(SELECTOR.EDITSECTION).focus(); 959 }); 960 961 // This hack is to keep Behat happy until they release a version of 962 // MinkSelenium2Driver that fixes 963 // https://github.com/Behat/MinkSelenium2Driver/issues/80. 964 if (!Y.one('input[name=section]')) { 965 Y.one('body').append('<input type="text" name="section" style="display: none">'); 966 } 967 }, 968 969 /** 970 * Edit the edit shuffle questions for the section 971 * 972 * @protected 973 * @method edit_shuffle_questions 974 * @param {EventFacade} ev The event that was fired. 975 * @param {Node} button The button that triggered this action. 976 * @param {Node} activity The activity node that this action will be performed on. 977 * @param {String} action The action that has been requested. 978 * @return Boolean 979 */ 980 edit_shuffle_questions: function(ev, button, activity) { 981 var newvalue; 982 if (activity.one(SELECTOR.EDITSHUFFLEQUESTIONSACTION).get('checked')) { 983 newvalue = 1; 984 } else { 985 newvalue = 0; 986 } 987 988 // Get the element we're working on 989 var data = { 990 'class': 'section', 991 'field': 'updateshufflequestions', 992 'id': activity.get('id').replace('section-', ''), 993 'newshuffle': newvalue 994 }; 995 996 // Prevent the default actions. 997 ev.preventDefault(); 998 999 // Send request. 1000 var spinner = M.util.add_spinner(Y, activity.one(SELECTOR.EDITSHUFFLEAREA)); 1001 this.send_request(data, spinner); 1002 } 1003 1004 }, { 1005 NAME: 'mod_quiz-section-toolbox', 1006 ATTRS: { 1007 courseid: { 1008 'value': 0 1009 }, 1010 quizid: { 1011 'value': 0 1012 } 1013 } 1014 }); 1015 1016 M.mod_quiz.init_section_toolbox = function(config) { 1017 return new SECTIONTOOLBOX(config); 1018 }; 1019 1020 1021 }, '@VERSION@', { 1022 "requires": [ 1023 "base", 1024 "node", 1025 "event", 1026 "event-key", 1027 "io", 1028 "moodle-mod_quiz-quizbase", 1029 "moodle-mod_quiz-util-slot", 1030 "moodle-core-notification-ajaxexception" 1031 ] 1032 });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |