[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 // This file is part of Moodle - http://moodle.org/ 2 // 3 // Moodle is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 // 8 // Moodle is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 // 13 // You should have received a copy of the GNU General Public License 14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 15 16 /** 17 * Javascript library for enableing a drag and drop upload to courses 18 * 19 * @package core 20 * @subpackage course 21 * @copyright 2012 Davo Smith 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 M.course_dndupload = { 25 // YUI object. 26 Y: null, 27 // URL for upload requests 28 url: M.cfg.wwwroot + '/course/dndupload.php', 29 // maximum size of files allowed in this form 30 maxbytes: 0, 31 // ID of the course we are on 32 courseid: null, 33 // Data about the different file/data handlers that are available 34 handlers: null, 35 // Nasty hack to distinguish between dragenter(first entry), 36 // dragenter+dragleave(moving between child elements) and dragleave (leaving element) 37 entercount: 0, 38 // Used to keep track of the section we are dragging across - to make 39 // spotting movement between sections more reliable 40 currentsection: null, 41 // Used to store the pending uploads whilst the user is being asked for further input 42 uploadqueue: null, 43 // True if the there is currently a dialog being shown (asking for a name, or giving a 44 // choice of file handlers) 45 uploaddialog: false, 46 // An array containing the last selected file handler for each file type 47 lastselected: null, 48 49 // The following are used to identify specific parts of the course page 50 51 // The type of HTML element that is a course section 52 sectiontypename: 'li', 53 // The classes that an element must have to be identified as a course section 54 sectionclasses: ['section', 'main'], 55 // The ID of the main content area of the page (for adding the 'status' div) 56 pagecontentid: 'page', 57 // The selector identifying the list of modules within a section (note changing this may require 58 // changes to the get_mods_element function) 59 modslistselector: 'ul.section', 60 61 /** 62 * Initalise the drag and drop upload interface 63 * Note: one and only one of options.filemanager and options.formcallback must be defined 64 * 65 * @param Y the YUI object 66 * @param object options { 67 * courseid: ID of the course we are on 68 * maxbytes: maximum size of files allowed in this form 69 * handlers: Data about the different file/data handlers that are available 70 * } 71 */ 72 init: function(Y, options) { 73 this.Y = Y; 74 75 if (!this.browser_supported()) { 76 return; // Browser does not support the required functionality 77 } 78 79 this.maxbytes = options.maxbytes; 80 this.courseid = options.courseid; 81 this.handlers = options.handlers; 82 this.uploadqueue = new Array(); 83 this.lastselected = new Array(); 84 85 var sectionselector = this.sectiontypename + '.' + this.sectionclasses.join('.'); 86 var sections = this.Y.all(sectionselector); 87 if (sections.isEmpty()) { 88 return; // No sections - incompatible course format or front page. 89 } 90 sections.each( function(el) { 91 this.add_preview_element(el); 92 this.init_events(el); 93 }, this); 94 95 if (options.showstatus) { 96 this.add_status_div(); 97 } 98 }, 99 100 /** 101 * Add a div element to tell the user that drag and drop upload 102 * is available (or to explain why it is not available) 103 */ 104 add_status_div: function() { 105 var Y = this.Y, 106 coursecontents = Y.one('#' + this.pagecontentid), 107 div, 108 handlefile = (this.handlers.filehandlers.length > 0), 109 handletext = false, 110 handlelink = false, 111 i = 0, 112 styletop, 113 styletopunit; 114 115 if (!coursecontents) { 116 return; 117 } 118 119 div = Y.Node.create('<div id="dndupload-status"></div>').setStyle('opacity', '0.0'); 120 coursecontents.insert(div, 0); 121 122 for (i = 0; i < this.handlers.types.length; i++) { 123 switch (this.handlers.types[i].identifier) { 124 case 'text': 125 case 'text/html': 126 handletext = true; 127 break; 128 case 'url': 129 handlelink = true; 130 break; 131 } 132 } 133 $msgident = 'dndworking'; 134 if (handlefile) { 135 $msgident += 'file'; 136 } 137 if (handletext) { 138 $msgident += 'text'; 139 } 140 if (handlelink) { 141 $msgident += 'link'; 142 } 143 div.setContent(M.util.get_string($msgident, 'moodle')); 144 145 styletop = div.getStyle('top') || '0px'; 146 styletopunit = styletop.replace(/^\d+/, ''); 147 styletop = parseInt(styletop.replace(/\D*$/, ''), 10); 148 149 var fadeanim = new Y.Anim({ 150 node: '#dndupload-status', 151 from: { 152 opacity: 0.0, 153 top: (styletop - 30).toString() + styletopunit 154 }, 155 156 to: { 157 opacity: 1.0, 158 top: styletop.toString() + styletopunit 159 }, 160 duration: 0.5 161 }); 162 fadeanim.once('end', function(e) { 163 this.set('reverse', 1); 164 Y.later(3000, this, 'run', null, false); 165 }); 166 fadeanim.run(); 167 }, 168 169 /** 170 * Check the browser has the required functionality 171 * @return true if browser supports drag/drop upload 172 */ 173 browser_supported: function() { 174 if (typeof FileReader == 'undefined') { 175 return false; 176 } 177 if (typeof FormData == 'undefined') { 178 return false; 179 } 180 return true; 181 }, 182 183 /** 184 * Initialise drag events on node container, all events need 185 * to be processed for drag and drop to work 186 * @param el the element to add events to 187 */ 188 init_events: function(el) { 189 this.Y.on('dragenter', this.drag_enter, el, this); 190 this.Y.on('dragleave', this.drag_leave, el, this); 191 this.Y.on('dragover', this.drag_over, el, this); 192 this.Y.on('drop', this.drop, el, this); 193 }, 194 195 /** 196 * Work out which course section a given element is in 197 * @param el the child DOM element within the section 198 * @return the DOM element representing the section 199 */ 200 get_section: function(el) { 201 var sectionclasses = this.sectionclasses; 202 return el.ancestor( function(test) { 203 var i; 204 for (i=0; i<sectionclasses.length; i++) { 205 if (!test.hasClass(sectionclasses[i])) { 206 return false; 207 } 208 return true; 209 } 210 }, true); 211 }, 212 213 /** 214 * Work out the number of the section we have been dropped on to, from the section element 215 * @param DOMElement section the selected section 216 * @return int the section number 217 */ 218 get_section_number: function(section) { 219 var sectionid = section.get('id').split('-'); 220 if (sectionid.length < 2 || sectionid[0] != 'section') { 221 return false; 222 } 223 return parseInt(sectionid[1]); 224 }, 225 226 /** 227 * Check if the event includes data of the given type 228 * @param e the event details 229 * @param type the data type to check for 230 * @return true if the data type is found in the event data 231 */ 232 types_includes: function(e, type) { 233 var i; 234 var types = e._event.dataTransfer.types; 235 type = type.toLowerCase(); 236 for (i=0; i<types.length; i++) { 237 if (!types.hasOwnProperty(i)) { 238 continue; 239 } 240 if (types[i].toLowerCase() === type) { 241 return true; 242 } 243 } 244 return false; 245 }, 246 247 /** 248 * Look through the event data, checking it against the registered data types 249 * (in order of priority) and return details of the first matching data type 250 * @param e the event details 251 * @return object|false - false if not found or an object { 252 * realtype: the type as given by the browser 253 * addmessage: the message to show to the user during dragging 254 * namemessage: the message for requesting a name for the resource from the user 255 * type: the identifier of the type (may match several 'realtype's) 256 * } 257 */ 258 drag_type: function(e) { 259 // Check there is some data attached. 260 if (e._event.dataTransfer === null) { 261 return false; 262 } 263 if (e._event.dataTransfer.types === null) { 264 return false; 265 } 266 if (e._event.dataTransfer.types.length == 0) { 267 return false; 268 } 269 270 // Check for files first. 271 if (this.types_includes(e, 'Files')) { 272 if (e.type != 'drop' || e._event.dataTransfer.files.length != 0) { 273 if (this.handlers.filehandlers.length == 0) { 274 return false; // No available file handlers - ignore this drag. 275 } 276 return { 277 realtype: 'Files', 278 addmessage: M.util.get_string('addfilehere', 'moodle'), 279 namemessage: null, // Should not be asked for anyway 280 type: 'Files' 281 }; 282 } 283 } 284 285 // Check each of the registered types. 286 var types = this.handlers.types; 287 for (var i=0; i<types.length; i++) { 288 // Check each of the different identifiers for this type 289 var dttypes = types[i].datatransfertypes; 290 for (var j=0; j<dttypes.length; j++) { 291 if (this.types_includes(e, dttypes[j])) { 292 return { 293 realtype: dttypes[j], 294 addmessage: types[i].addmessage, 295 namemessage: types[i].namemessage, 296 handlermessage: types[i].handlermessage, 297 type: types[i].identifier, 298 handlers: types[i].handlers 299 }; 300 } 301 } 302 } 303 return false; // No types we can handle 304 }, 305 306 /** 307 * Check the content of the drag/drop includes a type we can handle, then, if 308 * it is, notify the browser that we want to handle it 309 * @param event e 310 * @return string type of the event or false 311 */ 312 check_drag: function(e) { 313 var type = this.drag_type(e); 314 if (type) { 315 // Notify browser that we will handle this drag/drop 316 e.stopPropagation(); 317 e.preventDefault(); 318 } 319 return type; 320 }, 321 322 /** 323 * Handle a dragenter event: add a suitable 'add here' message 324 * when a drag event occurs, containing a registered data type 325 * @param e event data 326 * @return false to prevent the event from continuing to be processed 327 */ 328 drag_enter: function(e) { 329 if (!(type = this.check_drag(e))) { 330 return false; 331 } 332 333 var section = this.get_section(e.currentTarget); 334 if (!section) { 335 return false; 336 } 337 338 if (this.currentsection && this.currentsection != section) { 339 this.currentsection = section; 340 this.entercount = 1; 341 } else { 342 this.entercount++; 343 if (this.entercount > 2) { 344 this.entercount = 2; 345 return false; 346 } 347 } 348 349 this.show_preview_element(section, type); 350 351 return false; 352 }, 353 354 /** 355 * Handle a dragleave event: remove the 'add here' message (if present) 356 * @param e event data 357 * @return false to prevent the event from continuing to be processed 358 */ 359 drag_leave: function(e) { 360 if (!this.check_drag(e)) { 361 return false; 362 } 363 364 this.entercount--; 365 if (this.entercount == 1) { 366 return false; 367 } 368 this.entercount = 0; 369 this.currentsection = null; 370 371 this.hide_preview_element(); 372 return false; 373 }, 374 375 /** 376 * Handle a dragover event: just prevent the browser default (necessary 377 * to allow drag and drop handling to work) 378 * @param e event data 379 * @return false to prevent the event from continuing to be processed 380 */ 381 drag_over: function(e) { 382 this.check_drag(e); 383 return false; 384 }, 385 386 /** 387 * Handle a drop event: hide the 'add here' message, check the attached 388 * data type and start the upload process 389 * @param e event data 390 * @return false to prevent the event from continuing to be processed 391 */ 392 drop: function(e) { 393 if (!(type = this.check_drag(e))) { 394 return false; 395 } 396 397 this.hide_preview_element(); 398 399 // Work out the number of the section we are on (from its id) 400 var section = this.get_section(e.currentTarget); 401 var sectionnumber = this.get_section_number(section); 402 403 // Process the file or the included data 404 if (type.type == 'Files') { 405 var files = e._event.dataTransfer.files; 406 for (var i=0, f; f=files[i]; i++) { 407 this.handle_file(f, section, sectionnumber); 408 } 409 } else { 410 var contents = e._event.dataTransfer.getData(type.realtype); 411 if (contents) { 412 this.handle_item(type, contents, section, sectionnumber); 413 } 414 } 415 416 return false; 417 }, 418 419 /** 420 * Find or create the 'ul' element that contains all of the module 421 * instances in this section 422 * @param section the DOM element representing the section 423 * @return false to prevent the event from continuing to be processed 424 */ 425 get_mods_element: function(section) { 426 // Find the 'ul' containing the list of mods 427 var modsel = section.one(this.modslistselector); 428 if (!modsel) { 429 // Create the above 'ul' if it doesn't exist 430 modsel = document.createElement('ul'); 431 modsel.className = 'section img-text'; 432 var contentel = section.get('children').pop(); 433 var brel = contentel.get('children').pop(); 434 contentel.insertBefore(modsel, brel); 435 modsel = this.Y.one(modsel); 436 } 437 438 return modsel; 439 }, 440 441 /** 442 * Add a new dummy item to the list of mods, to be replaced by a real 443 * item & link once the AJAX upload call has completed 444 * @param name the label to show in the element 445 * @param section the DOM element reperesenting the course section 446 * @return DOM element containing the new item 447 */ 448 add_resource_element: function(name, section, module) { 449 var modsel = this.get_mods_element(section); 450 451 var resel = { 452 parent: modsel, 453 li: document.createElement('li'), 454 div: document.createElement('div'), 455 indentdiv: document.createElement('div'), 456 a: document.createElement('a'), 457 icon: document.createElement('img'), 458 namespan: document.createElement('span'), 459 groupingspan: document.createElement('span'), 460 progressouter: document.createElement('span'), 461 progress: document.createElement('span') 462 }; 463 464 resel.li.className = 'activity ' + module + ' modtype_' + module; 465 466 resel.indentdiv.className = 'mod-indent'; 467 resel.li.appendChild(resel.indentdiv); 468 469 resel.div.className = 'activityinstance'; 470 resel.indentdiv.appendChild(resel.div); 471 472 resel.a.href = '#'; 473 resel.div.appendChild(resel.a); 474 475 resel.icon.src = M.util.image_url('i/ajaxloader'); 476 resel.icon.className = 'activityicon iconlarge'; 477 resel.a.appendChild(resel.icon); 478 479 resel.namespan.className = 'instancename'; 480 resel.namespan.innerHTML = name; 481 resel.a.appendChild(resel.namespan); 482 483 resel.groupingspan.className = 'groupinglabel'; 484 resel.div.appendChild(resel.groupingspan); 485 486 resel.progressouter.className = 'dndupload-progress-outer'; 487 resel.progress.className = 'dndupload-progress-inner'; 488 resel.progress.innerHTML = ' '; 489 resel.progressouter.appendChild(resel.progress); 490 resel.div.appendChild(resel.progressouter); 491 492 modsel.insertBefore(resel.li, modsel.get('children').pop()); // Leave the 'preview element' at the bottom 493 494 return resel; 495 }, 496 497 /** 498 * Hide any visible dndupload-preview elements on the page 499 */ 500 hide_preview_element: function() { 501 this.Y.all('li.dndupload-preview').addClass('dndupload-hidden'); 502 this.Y.all('.dndupload-over').removeClass('dndupload-over'); 503 }, 504 505 /** 506 * Unhide the preview element for the given section and set it to display 507 * the correct message 508 * @param section the YUI node representing the selected course section 509 * @param type the details of the data type detected in the drag (including the message to display) 510 */ 511 show_preview_element: function(section, type) { 512 this.hide_preview_element(); 513 var preview = section.one('li.dndupload-preview').removeClass('dndupload-hidden'); 514 section.addClass('dndupload-over'); 515 516 // Horrible work-around to allow the 'Add X here' text to be a drop target in Firefox. 517 var node = preview.one('span').getDOMNode(); 518 node.firstChild.nodeValue = type.addmessage; 519 }, 520 521 /** 522 * Add the preview element to a course section. Note: this needs to be done before 'addEventListener' 523 * is called, otherwise Firefox will ignore events generated when the mouse is over the preview 524 * element (instead of passing them up to the parent element) 525 * @param section the YUI node representing the selected course section 526 */ 527 add_preview_element: function(section) { 528 var modsel = this.get_mods_element(section); 529 var preview = { 530 li: document.createElement('li'), 531 div: document.createElement('div'), 532 icon: document.createElement('img'), 533 namespan: document.createElement('span') 534 }; 535 536 preview.li.className = 'dndupload-preview dndupload-hidden'; 537 538 preview.div.className = 'mod-indent'; 539 preview.li.appendChild(preview.div); 540 541 preview.icon.src = M.util.image_url('t/addfile'); 542 preview.icon.className = 'icon'; 543 preview.div.appendChild(preview.icon); 544 545 preview.div.appendChild(document.createTextNode(' ')); 546 547 preview.namespan.className = 'instancename'; 548 preview.namespan.innerHTML = M.util.get_string('addfilehere', 'moodle'); 549 preview.div.appendChild(preview.namespan); 550 551 modsel.appendChild(preview.li); 552 }, 553 554 /** 555 * Find the registered handler for the given file type. If there is more than one, ask the 556 * user which one to use. Then upload the file to the server 557 * @param file the details of the file, taken from the FileList in the drop event 558 * @param section the DOM element representing the selected course section 559 * @param sectionnumber the number of the selected course section 560 */ 561 handle_file: function(file, section, sectionnumber) { 562 var handlers = new Array(); 563 var filehandlers = this.handlers.filehandlers; 564 var extension = ''; 565 var dotpos = file.name.lastIndexOf('.'); 566 if (dotpos != -1) { 567 extension = file.name.substr(dotpos+1, file.name.length).toLowerCase(); 568 } 569 570 for (var i=0; i<filehandlers.length; i++) { 571 if (filehandlers[i].extension == '*' || filehandlers[i].extension == extension) { 572 handlers.push(filehandlers[i]); 573 } 574 } 575 576 if (handlers.length == 0) { 577 // No handlers at all (not even 'resource'?) 578 return; 579 } 580 581 if (handlers.length == 1) { 582 this.upload_file(file, section, sectionnumber, handlers[0].module); 583 return; 584 } 585 586 this.file_handler_dialog(handlers, extension, file, section, sectionnumber); 587 }, 588 589 /** 590 * Show a dialog box, allowing the user to choose what to do with the file they are uploading 591 * @param handlers the available handlers to choose between 592 * @param extension the extension of the file being uploaded 593 * @param file the File object being uploaded 594 * @param section the DOM element of the section being uploaded to 595 * @param sectionnumber the number of the selected course section 596 */ 597 file_handler_dialog: function(handlers, extension, file, section, sectionnumber) { 598 if (this.uploaddialog) { 599 var details = new Object(); 600 details.isfile = true; 601 details.handlers = handlers; 602 details.extension = extension; 603 details.file = file; 604 details.section = section; 605 details.sectionnumber = sectionnumber; 606 this.uploadqueue.push(details); 607 return; 608 } 609 this.uploaddialog = true; 610 611 var timestamp = new Date().getTime(); 612 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp; 613 var content = ''; 614 var sel; 615 if (extension in this.lastselected) { 616 sel = this.lastselected[extension]; 617 } else { 618 sel = handlers[0].module; 619 } 620 content += '<p>'+M.util.get_string('actionchoice', 'moodle', file.name)+'</p>'; 621 content += '<div id="dndupload_handlers'+uploadid+'">'; 622 for (var i=0; i<handlers.length; i++) { 623 var id = 'dndupload_handler'+uploadid+handlers[i].module; 624 var checked = (handlers[i].module == sel) ? 'checked="checked" ' : ''; 625 content += '<input type="radio" name="handler" value="'+handlers[i].module+'" id="'+id+'" '+checked+'/>'; 626 content += ' <label for="'+id+'">'; 627 content += handlers[i].message; 628 content += '</label><br/>'; 629 } 630 content += '</div>'; 631 632 var Y = this.Y; 633 var self = this; 634 var panel = new M.core.dialogue({ 635 bodyContent: content, 636 width: '350px', 637 modal: true, 638 visible: true, 639 render: true, 640 align: { 641 node: null, 642 points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC] 643 } 644 }); 645 // When the panel is hidden - destroy it and then check for other pending uploads 646 panel.after("visibleChange", function(e) { 647 if (!panel.get('visible')) { 648 panel.destroy(true); 649 self.check_upload_queue(); 650 } 651 }); 652 653 // Add the submit/cancel buttons to the bottom of the dialog. 654 panel.addButton({ 655 label: M.util.get_string('upload', 'moodle'), 656 action: function(e) { 657 e.preventDefault(); 658 // Find out which module was selected 659 var module = false; 660 var div = Y.one('#dndupload_handlers'+uploadid); 661 div.all('input').each(function(input) { 662 if (input.get('checked')) { 663 module = input.get('value'); 664 } 665 }); 666 if (!module) { 667 return; 668 } 669 panel.hide(); 670 // Remember this selection for next time 671 self.lastselected[extension] = module; 672 // Do the upload 673 self.upload_file(file, section, sectionnumber, module); 674 }, 675 section: Y.WidgetStdMod.FOOTER 676 }); 677 panel.addButton({ 678 label: M.util.get_string('cancel', 'moodle'), 679 action: function(e) { 680 e.preventDefault(); 681 panel.hide(); 682 }, 683 section: Y.WidgetStdMod.FOOTER 684 }); 685 }, 686 687 /** 688 * Check to see if there are any other dialog boxes to show, now that the current one has 689 * been dealt with 690 */ 691 check_upload_queue: function() { 692 this.uploaddialog = false; 693 if (this.uploadqueue.length == 0) { 694 return; 695 } 696 697 var details = this.uploadqueue.shift(); 698 if (details.isfile) { 699 this.file_handler_dialog(details.handlers, details.extension, details.file, details.section, details.sectionnumber); 700 } else { 701 this.handle_item(details.type, details.contents, details.section, details.sectionnumber); 702 } 703 }, 704 705 /** 706 * Do the file upload: show the dummy element, use an AJAX call to send the data 707 * to the server, update the progress bar for the file, then replace the dummy 708 * element with the real information once the AJAX call completes 709 * @param file the details of the file, taken from the FileList in the drop event 710 * @param section the DOM element representing the selected course section 711 * @param sectionnumber the number of the selected course section 712 */ 713 upload_file: function(file, section, sectionnumber, module) { 714 715 // This would be an ideal place to use the Y.io function 716 // however, this does not support data encoded using the 717 // FormData object, which is needed to transfer data from 718 // the DataTransfer object into an XMLHTTPRequest 719 // This can be converted when the YUI issue has been integrated: 720 // http://yuilibrary.com/projects/yui3/ticket/2531274 721 var xhr = new XMLHttpRequest(); 722 var self = this; 723 724 if (file.size > this.maxbytes) { 725 alert("'"+file.name+"' "+M.util.get_string('filetoolarge', 'moodle')); 726 return; 727 } 728 729 // Add the file to the display 730 var resel = this.add_resource_element(file.name, section, module); 731 732 // Update the progress bar as the file is uploaded 733 xhr.upload.addEventListener('progress', function(e) { 734 if (e.lengthComputable) { 735 var percentage = Math.round((e.loaded * 100) / e.total); 736 resel.progress.style.width = percentage + '%'; 737 } 738 }, false); 739 740 // Wait for the AJAX call to complete, then update the 741 // dummy element with the returned details 742 xhr.onreadystatechange = function() { 743 if (xhr.readyState == 4) { 744 if (xhr.status == 200) { 745 var result = JSON.parse(xhr.responseText); 746 if (result) { 747 if (result.error == 0) { 748 // All OK - replace the dummy element. 749 resel.li.outerHTML = result.fullcontent; 750 if (self.Y.UA.gecko > 0) { 751 // Fix a Firefox bug which makes sites with a '~' in their wwwroot 752 // log the user out when clicking on the link (before refreshing the page). 753 resel.li.outerHTML = unescape(resel.li.outerHTML); 754 } 755 self.add_editing(result.elementid); 756 } else { 757 // Error - remove the dummy element 758 resel.parent.removeChild(resel.li); 759 alert(result.error); 760 } 761 } 762 } else { 763 alert(M.util.get_string('servererror', 'moodle')); 764 } 765 } 766 }; 767 768 // Prepare the data to send 769 var formData = new FormData(); 770 formData.append('repo_upload_file', file); 771 formData.append('sesskey', M.cfg.sesskey); 772 formData.append('course', this.courseid); 773 formData.append('section', sectionnumber); 774 formData.append('module', module); 775 formData.append('type', 'Files'); 776 777 // Send the AJAX call 778 xhr.open("POST", this.url, true); 779 xhr.send(formData); 780 }, 781 782 /** 783 * Show a dialog box to gather the name of the resource / activity to be created 784 * from the uploaded content 785 * @param type the details of the type of content 786 * @param contents the contents to be uploaded 787 * @section the DOM element for the section being uploaded to 788 * @sectionnumber the number of the section being uploaded to 789 */ 790 handle_item: function(type, contents, section, sectionnumber) { 791 if (type.handlers.length == 0) { 792 // Nothing to handle this - should not have got here 793 return; 794 } 795 796 if (type.handlers.length == 1 && type.handlers[0].noname) { 797 // Only one handler and it doesn't need a name (i.e. a label). 798 this.upload_item('', type.type, contents, section, sectionnumber, type.handlers[0].module); 799 this.check_upload_queue(); 800 return; 801 } 802 803 if (this.uploaddialog) { 804 var details = new Object(); 805 details.isfile = false; 806 details.type = type; 807 details.contents = contents; 808 details.section = section; 809 details.setcionnumber = sectionnumber; 810 this.uploadqueue.push(details); 811 return; 812 } 813 this.uploaddialog = true; 814 815 var timestamp = new Date().getTime(); 816 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp; 817 var nameid = 'dndupload_handler_name'+uploadid; 818 var content = ''; 819 if (type.handlers.length > 1) { 820 content += '<p>'+type.handlermessage+'</p>'; 821 content += '<div id="dndupload_handlers'+uploadid+'">'; 822 var sel = type.handlers[0].module; 823 for (var i=0; i<type.handlers.length; i++) { 824 var id = 'dndupload_handler'+uploadid+type.handlers[i].module; 825 var checked = (type.handlers[i].module == sel) ? 'checked="checked" ' : ''; 826 content += '<input type="radio" name="handler" value="'+i+'" id="'+id+'" '+checked+'/>'; 827 content += ' <label for="'+id+'">'; 828 content += type.handlers[i].message; 829 content += '</label><br/>'; 830 } 831 content += '</div>'; 832 } 833 var disabled = (type.handlers[0].noname) ? ' disabled = "disabled" ' : ''; 834 content += '<label for="'+nameid+'">'+type.namemessage+'</label>'; 835 content += ' <input type="text" id="'+nameid+'" value="" '+disabled+' />'; 836 837 var Y = this.Y; 838 var self = this; 839 var panel = new M.core.dialogue({ 840 bodyContent: content, 841 width: '350px', 842 modal: true, 843 visible: true, 844 render: true, 845 align: { 846 node: null, 847 points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC] 848 } 849 }); 850 851 // When the panel is hidden - destroy it and then check for other pending uploads 852 panel.after("visibleChange", function(e) { 853 if (!panel.get('visible')) { 854 panel.destroy(true); 855 self.check_upload_queue(); 856 } 857 }); 858 859 var namefield = Y.one('#'+nameid); 860 var submit = function(e) { 861 e.preventDefault(); 862 var name = Y.Lang.trim(namefield.get('value')); 863 var module = false; 864 var noname = false; 865 if (type.handlers.length > 1) { 866 // Find out which module was selected 867 var div = Y.one('#dndupload_handlers'+uploadid); 868 div.all('input').each(function(input) { 869 if (input.get('checked')) { 870 var idx = input.get('value'); 871 module = type.handlers[idx].module; 872 noname = type.handlers[idx].noname; 873 } 874 }); 875 if (!module) { 876 return; 877 } 878 } else { 879 module = type.handlers[0].module; 880 noname = type.handlers[0].noname; 881 } 882 if (name == '' && !noname) { 883 return; 884 } 885 if (noname) { 886 name = ''; 887 } 888 panel.hide(); 889 // Do the upload 890 self.upload_item(name, type.type, contents, section, sectionnumber, module); 891 }; 892 893 // Add the submit/cancel buttons to the bottom of the dialog. 894 panel.addButton({ 895 label: M.util.get_string('upload', 'moodle'), 896 action: submit, 897 section: Y.WidgetStdMod.FOOTER, 898 name: 'submit' 899 }); 900 panel.addButton({ 901 label: M.util.get_string('cancel', 'moodle'), 902 action: function(e) { 903 e.preventDefault(); 904 panel.hide(); 905 }, 906 section: Y.WidgetStdMod.FOOTER 907 }); 908 var submitbutton = panel.getButton('submit').button; 909 namefield.on('key', submit, 'enter'); // Submit the form if 'enter' pressed 910 namefield.after('keyup', function() { 911 if (Y.Lang.trim(namefield.get('value')) == '') { 912 submitbutton.disable(); 913 } else { 914 submitbutton.enable(); 915 } 916 }); 917 918 // Enable / disable the 'name' box, depending on the handler selected. 919 for (i=0; i<type.handlers.length; i++) { 920 if (type.handlers[i].noname) { 921 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) { 922 namefield.set('disabled', 'disabled'); 923 submitbutton.enable(); 924 }); 925 } else { 926 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) { 927 namefield.removeAttribute('disabled'); 928 namefield.focus(); 929 if (Y.Lang.trim(namefield.get('value')) == '') { 930 submitbutton.disable(); 931 } 932 }); 933 } 934 } 935 936 // Focus on the 'name' box 937 Y.one('#'+nameid).focus(); 938 }, 939 940 /** 941 * Upload any data types that are not files: display a dummy resource element, send 942 * the data to the server, update the progress bar for the file, then replace the 943 * dummy element with the real information once the AJAX call completes 944 * @param name the display name for the resource / activity to create 945 * @param type the details of the data type found in the drop event 946 * @param contents the actual data that was dropped 947 * @param section the DOM element representing the selected course section 948 * @param sectionnumber the number of the selected course section 949 * @param module the module chosen to handle this upload 950 */ 951 upload_item: function(name, type, contents, section, sectionnumber, module) { 952 953 // This would be an ideal place to use the Y.io function 954 // however, this does not support data encoded using the 955 // FormData object, which is needed to transfer data from 956 // the DataTransfer object into an XMLHTTPRequest 957 // This can be converted when the YUI issue has been integrated: 958 // http://yuilibrary.com/projects/yui3/ticket/2531274 959 var xhr = new XMLHttpRequest(); 960 var self = this; 961 962 // Add the item to the display 963 var resel = this.add_resource_element(name, section, module); 964 965 // Wait for the AJAX call to complete, then update the 966 // dummy element with the returned details 967 xhr.onreadystatechange = function() { 968 if (xhr.readyState == 4) { 969 if (xhr.status == 200) { 970 var result = JSON.parse(xhr.responseText); 971 if (result) { 972 if (result.error == 0) { 973 // All OK - replace the dummy element. 974 resel.li.outerHTML = result.fullcontent; 975 if (self.Y.UA.gecko > 0) { 976 // Fix a Firefox bug which makes sites with a '~' in their wwwroot 977 // log the user out when clicking on the link (before refreshing the page). 978 resel.li.outerHTML = unescape(resel.li.outerHTML); 979 } 980 self.add_editing(result.elementid); 981 } else { 982 // Error - remove the dummy element 983 resel.parent.removeChild(resel.li); 984 alert(result.error); 985 } 986 } 987 } else { 988 alert(M.util.get_string('servererror', 'moodle')); 989 } 990 } 991 }; 992 993 // Prepare the data to send 994 var formData = new FormData(); 995 formData.append('contents', contents); 996 formData.append('displayname', name); 997 formData.append('sesskey', M.cfg.sesskey); 998 formData.append('course', this.courseid); 999 formData.append('section', sectionnumber); 1000 formData.append('type', type); 1001 formData.append('module', module); 1002 1003 // Send the data 1004 xhr.open("POST", this.url, true); 1005 xhr.send(formData); 1006 }, 1007 1008 /** 1009 * Call the AJAX course editing initialisation to add the editing tools 1010 * to the newly-created resource link 1011 * @param elementid the id of the DOM element containing the new resource link 1012 * @param sectionnumber the number of the selected course section 1013 */ 1014 add_editing: function(elementid) { 1015 var node = Y.one('#' + elementid); 1016 YUI().use('moodle-course-coursebase', function(Y) { 1017 Y.log("Invoking setup_for_resource", 'debug', 'coursedndupload'); 1018 M.course.coursebase.invoke_function('setup_for_resource', node); 1019 }); 1020 if (M.core.actionmenu && M.core.actionmenu.newDOMNode) { 1021 M.core.actionmenu.newDOMNode(node); 1022 } 1023 } 1024 };
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 |