1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * Javascript library for enableing a drag and drop upload to courses
21 * @copyright 2012 Davo Smith
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 M.course_dndupload = {
27 // URL for upload requests
28 url: M.cfg.wwwroot + '/course/dndupload.php',
29 // maximum size of files allowed in this form
31 // ID of the course we are on
33 // Data about the different file/data handlers that are available
35 // Nasty hack to distinguish between dragenter(first entry),
36 // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
38 // Used to keep track of the section we are dragging across - to make
39 // spotting movement between sections more reliable
41 // Used to store the pending uploads whilst the user is being asked for further input
43 // True if the there is currently a dialog being shown (asking for a name, or giving a
44 // choice of file handlers)
46 // An array containing the last selected file handler for each file type
49 // The following are used to identify specific parts of the course page
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 // Original onbeforeunload event.
61 originalUnloadEvent: null,
64 * Initalise the drag and drop upload interface
65 * Note: one and only one of options.filemanager and options.formcallback must be defined
67 * @param Y the YUI object
68 * @param object options {
69 * courseid: ID of the course we are on
70 * maxbytes: maximum size of files allowed in this form
71 * handlers: Data about the different file/data handlers that are available
74 init: function(Y, options) {
77 if (!this.browser_supported()) {
78 return; // Browser does not support the required functionality
81 this.maxbytes = options.maxbytes;
82 this.courseid = options.courseid;
83 this.handlers = options.handlers;
84 this.uploadqueue = new Array();
85 this.lastselected = new Array();
87 var sectionselector = this.sectiontypename + '.' + this.sectionclasses.join('.');
88 var sections = this.Y.all(sectionselector);
89 if (sections.isEmpty()) {
90 return; // No sections - incompatible course format or front page.
92 sections.each( function(el) {
93 this.add_preview_element(el);
97 if (options.showstatus) {
98 this.add_status_div();
103 * Add a div element to tell the user that drag and drop upload
104 * is available (or to explain why it is not available)
106 add_status_div: function() {
108 coursecontents = Y.one('#' + this.pagecontentid),
110 handlefile = (this.handlers.filehandlers.length > 0),
117 if (!coursecontents) {
121 div = Y.Node.create('<div id="dndupload-status"></div>').setStyle('opacity', '0.0');
122 coursecontents.insert(div, 0);
124 for (i = 0; i < this.handlers.types.length; i++) {
125 switch (this.handlers.types[i].identifier) {
135 $msgident = 'dndworking';
145 div.setContent(M.util.get_string($msgident, 'moodle'));
147 styletop = div.getStyle('top') || '0px';
148 styletopunit = styletop.replace(/^\d+/, '');
149 styletop = parseInt(styletop.replace(/\D*$/, ''), 10);
151 var fadein = new Y.Anim({
152 node: '#dndupload-status',
155 top: (styletop - 30).toString() + styletopunit
160 top: styletop.toString() + styletopunit
165 var fadeout = new Y.Anim({
166 node: '#dndupload-status',
169 top: styletop.toString() + styletopunit
174 top: (styletop - 30).toString() + styletopunit
180 fadein.on('end', function(e) {
181 Y.later(3000, this, function() {
186 fadeout.on('end', function(e) {
187 Y.one('#dndupload-status').remove(true);
192 * Check the browser has the required functionality
193 * @return true if browser supports drag/drop upload
195 browser_supported: function() {
196 if (typeof FileReader == 'undefined') {
199 if (typeof FormData == 'undefined') {
206 * Initialise drag events on node container, all events need
207 * to be processed for drag and drop to work
208 * @param el the element to add events to
210 init_events: function(el) {
211 this.Y.on('dragenter', this.drag_enter, el, this);
212 this.Y.on('dragleave', this.drag_leave, el, this);
213 this.Y.on('dragover', this.drag_over, el, this);
214 this.Y.on('drop', this.drop, el, this);
218 * Work out which course section a given element is in
219 * @param el the child DOM element within the section
220 * @return the DOM element representing the section
222 get_section: function(el) {
223 var sectionclasses = this.sectionclasses;
224 return el.ancestor( function(test) {
226 for (i=0; i<sectionclasses.length; i++) {
227 if (!test.hasClass(sectionclasses[i])) {
236 * Work out the number of the section we have been dropped on to, from the section element
237 * @param DOMElement section the selected section
238 * @return int the section number
240 get_section_number: function(section) {
241 var sectionid = section.get('id').split('-');
242 if (sectionid.length < 2 || sectionid[0] != 'section') {
245 return parseInt(sectionid[1]);
249 * Check if the event includes data of the given type
250 * @param e the event details
251 * @param type the data type to check for
252 * @return true if the data type is found in the event data
254 types_includes: function(e, type) {
256 var types = e._event.dataTransfer.types;
257 type = type.toLowerCase();
258 for (i=0; i<types.length; i++) {
259 if (!types.hasOwnProperty(i)) {
262 if (types[i].toLowerCase() === type) {
270 * Look through the event data, checking it against the registered data types
271 * (in order of priority) and return details of the first matching data type
272 * @param e the event details
273 * @return object|false - false if not found or an object {
274 * realtype: the type as given by the browser
275 * addmessage: the message to show to the user during dragging
276 * namemessage: the message for requesting a name for the resource from the user
277 * type: the identifier of the type (may match several 'realtype's)
280 drag_type: function(e) {
281 // Check there is some data attached.
282 if (e._event.dataTransfer === null) {
285 if (e._event.dataTransfer.types === null) {
288 if (e._event.dataTransfer.types.length == 0) {
292 // Check for files first.
293 if (this.types_includes(e, 'Files')) {
294 if (e.type != 'drop' || e._event.dataTransfer.files.length != 0) {
295 if (this.handlers.filehandlers.length == 0) {
296 return false; // No available file handlers - ignore this drag.
300 addmessage: M.util.get_string('addfilehere', 'moodle'),
301 namemessage: null, // Should not be asked for anyway
307 // Check each of the registered types.
308 var types = this.handlers.types;
309 for (var i=0; i<types.length; i++) {
310 // Check each of the different identifiers for this type
311 var dttypes = types[i].datatransfertypes;
312 for (var j=0; j<dttypes.length; j++) {
313 if (this.types_includes(e, dttypes[j])) {
315 realtype: dttypes[j],
316 addmessage: types[i].addmessage,
317 namemessage: types[i].namemessage,
318 handlermessage: types[i].handlermessage,
319 type: types[i].identifier,
320 handlers: types[i].handlers
325 return false; // No types we can handle
329 * Check the content of the drag/drop includes a type we can handle, then, if
330 * it is, notify the browser that we want to handle it
332 * @return string type of the event or false
334 check_drag: function(e) {
335 var type = this.drag_type(e);
337 // Notify browser that we will handle this drag/drop
345 * Handle a dragenter event: add a suitable 'add here' message
346 * when a drag event occurs, containing a registered data type
347 * @param e event data
348 * @return false to prevent the event from continuing to be processed
350 drag_enter: function(e) {
351 if (!(type = this.check_drag(e))) {
355 var section = this.get_section(e.currentTarget);
360 if (this.currentsection && this.currentsection != section) {
361 this.currentsection = section;
365 if (this.entercount > 2) {
371 this.show_preview_element(section, type);
377 * Handle a dragleave event: remove the 'add here' message (if present)
378 * @param e event data
379 * @return false to prevent the event from continuing to be processed
381 drag_leave: function(e) {
382 if (!this.check_drag(e)) {
387 if (this.entercount == 1) {
391 this.currentsection = null;
393 this.hide_preview_element();
398 * Handle a dragover event: just prevent the browser default (necessary
399 * to allow drag and drop handling to work)
400 * @param e event data
401 * @return false to prevent the event from continuing to be processed
403 drag_over: function(e) {
409 * Handle a drop event: hide the 'add here' message, check the attached
410 * data type and start the upload process
411 * @param e event data
412 * @return false to prevent the event from continuing to be processed
415 this.hide_preview_element();
417 if (!(type = this.check_drag(e))) {
421 // Work out the number of the section we are on (from its id)
422 var section = this.get_section(e.currentTarget);
423 var sectionnumber = this.get_section_number(section);
425 // Process the file or the included data
426 if (type.type == 'Files') {
427 var files = e._event.dataTransfer.files;
428 for (var i=0, f; f=files[i]; i++) {
429 this.handle_file(f, section, sectionnumber);
432 var contents = e._event.dataTransfer.getData(type.realtype);
434 this.handle_item(type, contents, section, sectionnumber);
442 * Find or create the 'ul' element that contains all of the module
443 * instances in this section
444 * @param section the DOM element representing the section
445 * @return false to prevent the event from continuing to be processed
447 get_mods_element: function(section) {
448 // Find the 'ul' containing the list of mods
449 var modsel = section.one(this.modslistselector);
451 // Create the above 'ul' if it doesn't exist
452 modsel = document.createElement('ul');
453 modsel.className = 'section img-text';
454 var contentel = section.get('children').pop();
455 var brel = contentel.get('children').pop();
456 contentel.insertBefore(modsel, brel);
457 modsel = this.Y.one(modsel);
464 * Add a new dummy item to the list of mods, to be replaced by a real
465 * item & link once the AJAX upload call has completed
466 * @param name the label to show in the element
467 * @param section the DOM element reperesenting the course section
468 * @return DOM element containing the new item
470 add_resource_element: function(name, section, module) {
471 var modsel = this.get_mods_element(section);
475 li: document.createElement('li'),
476 div: document.createElement('div'),
477 indentdiv: document.createElement('div'),
478 a: document.createElement('a'),
479 icon: document.createElement('img'),
480 namespan: document.createElement('span'),
481 groupingspan: document.createElement('span'),
482 progressouter: document.createElement('span'),
483 progress: document.createElement('span')
486 resel.li.className = 'activity ' + module + ' modtype_' + module;
488 resel.indentdiv.className = 'mod-indent';
489 resel.li.appendChild(resel.indentdiv);
491 resel.div.className = 'activityinstance';
492 resel.indentdiv.appendChild(resel.div);
495 resel.div.appendChild(resel.a);
497 resel.icon.src = M.util.image_url('i/ajaxloader');
498 resel.icon.className = 'activityicon iconlarge';
499 resel.a.appendChild(resel.icon);
501 resel.namespan.className = 'instancename';
502 resel.namespan.innerHTML = name;
503 resel.a.appendChild(resel.namespan);
505 resel.groupingspan.className = 'groupinglabel';
506 resel.div.appendChild(resel.groupingspan);
508 resel.progressouter.className = 'dndupload-progress-outer';
509 resel.progress.className = 'dndupload-progress-inner';
510 resel.progress.innerHTML = ' ';
511 resel.progressouter.appendChild(resel.progress);
512 resel.div.appendChild(resel.progressouter);
514 modsel.insertBefore(resel.li, modsel.get('children').pop()); // Leave the 'preview element' at the bottom
520 * Hide any visible dndupload-preview elements on the page
522 hide_preview_element: function() {
523 this.Y.all('li.dndupload-preview').addClass('dndupload-hidden');
524 this.Y.all('.dndupload-over').removeClass('dndupload-over');
528 * Unhide the preview element for the given section and set it to display
529 * the correct message
530 * @param section the YUI node representing the selected course section
531 * @param type the details of the data type detected in the drag (including the message to display)
533 show_preview_element: function(section, type) {
534 this.hide_preview_element();
535 var preview = section.one('li.dndupload-preview').removeClass('dndupload-hidden');
536 section.addClass('dndupload-over');
538 // Horrible work-around to allow the 'Add X here' text to be a drop target in Firefox.
539 var node = preview.one('span').getDOMNode();
540 node.firstChild.nodeValue = type.addmessage;
544 * Add the preview element to a course section. Note: this needs to be done before 'addEventListener'
545 * is called, otherwise Firefox will ignore events generated when the mouse is over the preview
546 * element (instead of passing them up to the parent element)
547 * @param section the YUI node representing the selected course section
549 add_preview_element: function(section) {
550 var modsel = this.get_mods_element(section);
552 li: document.createElement('li'),
553 div: document.createElement('div'),
554 icon: document.createElement('img'),
555 namespan: document.createElement('span')
558 preview.li.className = 'dndupload-preview dndupload-hidden';
560 preview.div.className = 'mod-indent';
561 preview.li.appendChild(preview.div);
563 preview.icon.src = M.util.image_url('t/addfile');
564 preview.icon.className = 'icon';
565 preview.div.appendChild(preview.icon);
567 preview.div.appendChild(document.createTextNode(' '));
569 preview.namespan.className = 'instancename';
570 preview.namespan.innerHTML = M.util.get_string('addfilehere', 'moodle');
571 preview.div.appendChild(preview.namespan);
573 modsel.appendChild(preview.li);
577 * Find the registered handler for the given file type. If there is more than one, ask the
578 * user which one to use. Then upload the file to the server
579 * @param file the details of the file, taken from the FileList in the drop event
580 * @param section the DOM element representing the selected course section
581 * @param sectionnumber the number of the selected course section
583 handle_file: function(file, section, sectionnumber) {
584 var handlers = new Array();
585 var filehandlers = this.handlers.filehandlers;
587 var dotpos = file.name.lastIndexOf('.');
589 extension = file.name.substr(dotpos+1, file.name.length).toLowerCase();
592 for (var i=0; i<filehandlers.length; i++) {
593 if (filehandlers[i].extension == '*' || filehandlers[i].extension == extension) {
594 handlers.push(filehandlers[i]);
598 if (handlers.length == 0) {
599 // No handlers at all (not even 'resource'?)
603 if (handlers.length == 1) {
604 this.upload_file(file, section, sectionnumber, handlers[0].module);
608 this.file_handler_dialog(handlers, extension, file, section, sectionnumber);
612 * Show a dialog box, allowing the user to choose what to do with the file they are uploading
613 * @param handlers the available handlers to choose between
614 * @param extension the extension of the file being uploaded
615 * @param file the File object being uploaded
616 * @param section the DOM element of the section being uploaded to
617 * @param sectionnumber the number of the selected course section
619 file_handler_dialog: function(handlers, extension, file, section, sectionnumber) {
620 if (this.uploaddialog) {
621 var details = new Object();
622 details.isfile = true;
623 details.handlers = handlers;
624 details.extension = extension;
626 details.section = section;
627 details.sectionnumber = sectionnumber;
628 this.uploadqueue.push(details);
631 this.uploaddialog = true;
633 var timestamp = new Date().getTime();
634 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
637 if (extension in this.lastselected) {
638 sel = this.lastselected[extension];
640 sel = handlers[0].module;
642 content += '<p>'+M.util.get_string('actionchoice', 'moodle', file.name)+'</p>';
643 content += '<div id="dndupload_handlers'+uploadid+'">';
644 for (var i=0; i<handlers.length; i++) {
645 var id = 'dndupload_handler'+uploadid+handlers[i].module;
646 var checked = (handlers[i].module == sel) ? 'checked="checked" ' : '';
647 content += '<input type="radio" name="handler" value="'+handlers[i].module+'" id="'+id+'" '+checked+'/>';
648 content += ' <label for="'+id+'">';
649 content += handlers[i].message;
650 content += '</label><br/>';
656 var panel = new M.core.dialogue({
657 bodyContent: content,
664 points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC]
668 // When the panel is hidden - destroy it and then check for other pending uploads
669 panel.after("visibleChange", function(e) {
670 if (!panel.get('visible')) {
672 self.check_upload_queue();
676 // Add the submit/cancel buttons to the bottom of the dialog.
678 label: M.util.get_string('upload', 'moodle'),
679 action: function(e) {
681 // Find out which module was selected
683 var div = Y.one('#dndupload_handlers'+uploadid);
684 div.all('input').each(function(input) {
685 if (input.get('checked')) {
686 module = input.get('value');
693 // Remember this selection for next time
694 self.lastselected[extension] = module;
696 self.upload_file(file, section, sectionnumber, module);
698 section: Y.WidgetStdMod.FOOTER
701 label: M.util.get_string('cancel', 'moodle'),
702 action: function(e) {
706 section: Y.WidgetStdMod.FOOTER
711 * Check to see if there are any other dialog boxes to show, now that the current one has
714 check_upload_queue: function() {
715 this.uploaddialog = false;
716 if (this.uploadqueue.length == 0) {
720 var details = this.uploadqueue.shift();
721 if (details.isfile) {
722 this.file_handler_dialog(details.handlers, details.extension, details.file, details.section, details.sectionnumber);
724 this.handle_item(details.type, details.contents, details.section, details.sectionnumber);
729 * Do the file upload: show the dummy element, use an AJAX call to send the data
730 * to the server, update the progress bar for the file, then replace the dummy
731 * element with the real information once the AJAX call completes
732 * @param file the details of the file, taken from the FileList in the drop event
733 * @param section the DOM element representing the selected course section
734 * @param sectionnumber the number of the selected course section
736 upload_file: function(file, section, sectionnumber, module) {
738 // This would be an ideal place to use the Y.io function
739 // however, this does not support data encoded using the
740 // FormData object, which is needed to transfer data from
741 // the DataTransfer object into an XMLHTTPRequest
742 // This can be converted when the YUI issue has been integrated:
743 // http://yuilibrary.com/projects/yui3/ticket/2531274
744 var xhr = new XMLHttpRequest();
747 if (this.maxbytes > 0 && file.size > this.maxbytes) {
748 new M.core.alert({message: M.util.get_string('namedfiletoolarge', 'moodle', {filename: file.name})});
752 // Add the file to the display
753 var resel = this.add_resource_element(file.name, section, module);
755 // Update the progress bar as the file is uploaded
756 xhr.upload.addEventListener('progress', function(e) {
757 if (e.lengthComputable) {
758 var percentage = Math.round((e.loaded * 100) / e.total);
759 resel.progress.style.width = percentage + '%';
763 // Wait for the AJAX call to complete, then update the
764 // dummy element with the returned details
765 xhr.onreadystatechange = function() {
766 if (xhr.readyState == 1) {
767 this.originalUnloadEvent = window.onbeforeunload;
768 self.reportUploadDirtyState(true);
770 if (xhr.readyState == 4) {
771 if (xhr.status == 200) {
772 var result = JSON.parse(xhr.responseText);
774 if (result.error == 0) {
775 // All OK - replace the dummy element.
776 resel.li.outerHTML = result.fullcontent;
777 if (self.Y.UA.gecko > 0) {
778 // Fix a Firefox bug which makes sites with a '~' in their wwwroot
779 // log the user out when clicking on the link (before refreshing the page).
780 resel.li.outerHTML = unescape(resel.li.outerHTML);
782 self.add_editing(result.elementid);
783 // Fire the content updated event.
784 require(['core/event', 'jquery'], function(event, $) {
785 event.notifyFilterContentUpdated($(result.fullcontent));
788 // Error - remove the dummy element
789 resel.parent.removeChild(resel.li);
790 new M.core.alert({message: result.error});
794 new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
796 self.reportUploadDirtyState(false);
800 // Prepare the data to send
801 var formData = new FormData();
803 formData.append('repo_upload_file', file);
805 // Edge throws an error at this point if we try to upload a folder.
806 resel.parent.removeChild(resel.li);
807 new M.core.alert({message: M.util.get_string('filereaderror', 'moodle', file.name)});
810 formData.append('sesskey', M.cfg.sesskey);
811 formData.append('course', this.courseid);
812 formData.append('section', sectionnumber);
813 formData.append('module', module);
814 formData.append('type', 'Files');
816 // Try reading the file to check it is not a folder, before sending it to the server.
817 var reader = new FileReader();
818 reader.onload = function() {
819 // File was read OK - send it to the server.
820 xhr.open("POST", self.url, true);
823 reader.onerror = function() {
824 // Unable to read the file (it is probably a folder) - display an error message.
825 resel.parent.removeChild(resel.li);
826 new M.core.alert({message: M.util.get_string('filereaderror', 'moodle', file.name)});
829 // If this is a non-empty file, try reading the first few bytes.
830 // This will trigger reader.onerror() for folders and reader.onload() for ordinary, readable files.
831 reader.readAsText(file.slice(0, 5));
833 // If you call slice() on a 0-byte folder, before calling readAsText, then Firefox triggers reader.onload(),
834 // instead of reader.onerror().
835 // So, for 0-byte files, just call readAsText on the whole file (and it will trigger load/error functions as expected).
836 reader.readAsText(file);
841 * Show a dialog box to gather the name of the resource / activity to be created
842 * from the uploaded content
843 * @param type the details of the type of content
844 * @param contents the contents to be uploaded
845 * @section the DOM element for the section being uploaded to
846 * @sectionnumber the number of the section being uploaded to
848 handle_item: function(type, contents, section, sectionnumber) {
849 if (type.handlers.length == 0) {
850 // Nothing to handle this - should not have got here
854 if (type.handlers.length == 1 && type.handlers[0].noname) {
855 // Only one handler and it doesn't need a name (i.e. a label).
856 this.upload_item('', type.type, contents, section, sectionnumber, type.handlers[0].module);
857 this.check_upload_queue();
861 if (this.uploaddialog) {
862 var details = new Object();
863 details.isfile = false;
865 details.contents = contents;
866 details.section = section;
867 details.setcionnumber = sectionnumber;
868 this.uploadqueue.push(details);
871 this.uploaddialog = true;
873 var timestamp = new Date().getTime();
874 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
875 var nameid = 'dndupload_handler_name'+uploadid;
877 if (type.handlers.length > 1) {
878 content += '<p>'+type.handlermessage+'</p>';
879 content += '<div id="dndupload_handlers'+uploadid+'">';
880 var sel = type.handlers[0].module;
881 for (var i=0; i<type.handlers.length; i++) {
882 var id = 'dndupload_handler'+uploadid+type.handlers[i].module;
883 var checked = (type.handlers[i].module == sel) ? 'checked="checked" ' : '';
884 content += '<input type="radio" name="handler" value="'+i+'" id="'+id+'" '+checked+'/>';
885 content += ' <label for="'+id+'">';
886 content += type.handlers[i].message;
887 content += '</label><br/>';
891 var disabled = (type.handlers[0].noname) ? ' disabled = "disabled" ' : '';
892 content += '<label for="'+nameid+'">'+type.namemessage+'</label>';
893 content += ' <input type="text" id="'+nameid+'" value="" '+disabled+' />';
897 var panel = new M.core.dialogue({
898 bodyContent: content,
905 points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC]
909 // When the panel is hidden - destroy it and then check for other pending uploads
910 panel.after("visibleChange", function(e) {
911 if (!panel.get('visible')) {
913 self.check_upload_queue();
917 var namefield = Y.one('#'+nameid);
918 var submit = function(e) {
920 var name = Y.Lang.trim(namefield.get('value'));
923 if (type.handlers.length > 1) {
924 // Find out which module was selected
925 var div = Y.one('#dndupload_handlers'+uploadid);
926 div.all('input').each(function(input) {
927 if (input.get('checked')) {
928 var idx = input.get('value');
929 module = type.handlers[idx].module;
930 noname = type.handlers[idx].noname;
937 module = type.handlers[0].module;
938 noname = type.handlers[0].noname;
940 if (name == '' && !noname) {
948 self.upload_item(name, type.type, contents, section, sectionnumber, module);
951 // Add the submit/cancel buttons to the bottom of the dialog.
953 label: M.util.get_string('upload', 'moodle'),
955 section: Y.WidgetStdMod.FOOTER,
959 label: M.util.get_string('cancel', 'moodle'),
960 action: function(e) {
964 section: Y.WidgetStdMod.FOOTER
966 var submitbutton = panel.getButton('submit').button;
967 namefield.on('key', submit, 'enter'); // Submit the form if 'enter' pressed
968 namefield.after('keyup', function() {
969 if (Y.Lang.trim(namefield.get('value')) == '') {
970 submitbutton.disable();
972 submitbutton.enable();
976 // Enable / disable the 'name' box, depending on the handler selected.
977 for (i=0; i<type.handlers.length; i++) {
978 if (type.handlers[i].noname) {
979 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) {
980 namefield.set('disabled', 'disabled');
981 submitbutton.enable();
984 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) {
985 namefield.removeAttribute('disabled');
987 if (Y.Lang.trim(namefield.get('value')) == '') {
988 submitbutton.disable();
994 // Focus on the 'name' box
995 Y.one('#'+nameid).focus();
999 * Upload any data types that are not files: display a dummy resource element, send
1000 * the data to the server, update the progress bar for the file, then replace the
1001 * dummy element with the real information once the AJAX call completes
1002 * @param name the display name for the resource / activity to create
1003 * @param type the details of the data type found in the drop event
1004 * @param contents the actual data that was dropped
1005 * @param section the DOM element representing the selected course section
1006 * @param sectionnumber the number of the selected course section
1007 * @param module the module chosen to handle this upload
1009 upload_item: function(name, type, contents, section, sectionnumber, module) {
1011 // This would be an ideal place to use the Y.io function
1012 // however, this does not support data encoded using the
1013 // FormData object, which is needed to transfer data from
1014 // the DataTransfer object into an XMLHTTPRequest
1015 // This can be converted when the YUI issue has been integrated:
1016 // http://yuilibrary.com/projects/yui3/ticket/2531274
1017 var xhr = new XMLHttpRequest();
1020 // Add the item to the display
1021 var resel = this.add_resource_element(name, section, module);
1023 // Wait for the AJAX call to complete, then update the
1024 // dummy element with the returned details
1025 xhr.onreadystatechange = function() {
1026 if (xhr.readyState == 1) {
1027 this.originalUnloadEvent = window.onbeforeunload;
1028 self.reportUploadDirtyState(true);
1030 if (xhr.readyState == 4) {
1031 if (xhr.status == 200) {
1032 var result = JSON.parse(xhr.responseText);
1034 if (result.error == 0) {
1035 // All OK - replace the dummy element.
1036 resel.li.outerHTML = result.fullcontent;
1037 if (self.Y.UA.gecko > 0) {
1038 // Fix a Firefox bug which makes sites with a '~' in their wwwroot
1039 // log the user out when clicking on the link (before refreshing the page).
1040 resel.li.outerHTML = unescape(resel.li.outerHTML);
1042 self.add_editing(result.elementid);
1044 // Error - remove the dummy element
1045 resel.parent.removeChild(resel.li);
1046 new M.core.alert({message: result.error});
1050 new M.core.alert({message: M.util.get_string('servererror', 'moodle')});
1052 self.reportUploadDirtyState(false);
1056 // Prepare the data to send
1057 var formData = new FormData();
1058 formData.append('contents', contents);
1059 formData.append('displayname', name);
1060 formData.append('sesskey', M.cfg.sesskey);
1061 formData.append('course', this.courseid);
1062 formData.append('section', sectionnumber);
1063 formData.append('type', type);
1064 formData.append('module', module);
1067 xhr.open("POST", this.url, true);
1072 * Call the AJAX course editing initialisation to add the editing tools
1073 * to the newly-created resource link
1074 * @param elementid the id of the DOM element containing the new resource link
1075 * @param sectionnumber the number of the selected course section
1077 add_editing: function(elementid) {
1078 var node = Y.one('#' + elementid);
1079 YUI().use('moodle-course-coursebase', function(Y) {
1080 Y.log("Invoking setup_for_resource", 'debug', 'coursedndupload');
1081 M.course.coursebase.invoke_function('setup_for_resource', node);
1083 if (M.core.actionmenu && M.core.actionmenu.newDOMNode) {
1084 M.core.actionmenu.newDOMNode(node);
1089 * Set the event to prevent user navigate away when upload progress still running.
1091 * @param {bool} enable true if upload progress is running, false otherwise
1093 reportUploadDirtyState: function(enable) {
1095 window.onbeforeunload = this.originalUnloadEvent;
1097 window.onbeforeunload = function(e) {
1098 var warningMessage = M.util.get_string('changesmadereallygoaway', 'moodle');
1100 e.returnValue = warningMessage;
1102 return warningMessage;