Merge branch 'MDL-32087' of git://github.com/timhunt/moodle
[moodle.git] / course / dndupload.js
CommitLineData
32528f94
DS
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 *
a0a06d05 19 * @package core
32528f94
DS
20 * @subpackage course
21 * @copyright 2012 Davo Smith
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24M.course_dndupload = {
25 // YUI object.
26 Y: null,
27 // URL for upload requests
33b24bdd 28 url: M.cfg.wwwroot + '/course/dndupload.php',
32528f94
DS
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-content',
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 var div = this.add_status_div();
33b24bdd 96 div.setContent(M.util.get_string('dndworking', 'moodle'));
32528f94
DS
97 },
98
99 /**
100 * Add a div element to tell the user that drag and drop upload
101 * is available (or to explain why it is not available)
102 * @return the DOM element to add messages to
103 */
104 add_status_div: function() {
105 var div = document.createElement('div');
106 div.id = 'dndupload-status';
107 var coursecontents = document.getElementById(this.pagecontentid);
108 if (coursecontents) {
109 coursecontents.insertBefore(div, coursecontents.firstChild);
110 }
111 return this.Y.one(div);
112 },
113
114 /**
115 * Check the browser has the required functionality
116 * @return true if browser supports drag/drop upload
117 */
118 browser_supported: function() {
32528f94
DS
119 if (typeof FileReader == 'undefined') {
120 return false;
121 }
122 if (typeof FormData == 'undefined') {
123 return false;
124 }
125 return true;
126 },
127
128 /**
129 * Initialise drag events on node container, all events need
130 * to be processed for drag and drop to work
131 * @param el the element to add events to
132 */
133 init_events: function(el) {
134 this.Y.on('dragenter', this.drag_enter, el, this);
135 this.Y.on('dragleave', this.drag_leave, el, this);
136 this.Y.on('dragover', this.drag_over, el, this);
137 this.Y.on('drop', this.drop, el, this);
138 },
139
140 /**
141 * Work out which course section a given element is in
142 * @param el the child DOM element within the section
143 * @return the DOM element representing the section
144 */
145 get_section: function(el) {
146 var sectionclasses = this.sectionclasses;
147 return el.ancestor( function(test) {
148 var i;
149 for (i=0; i<sectionclasses.length; i++) {
150 if (!test.hasClass(sectionclasses[i])) {
151 return false;
152 }
153 return true;
154 }
155 }, true);
156 },
157
158 /**
159 * Work out the number of the section we have been dropped on to, from the section element
160 * @param DOMElement section the selected section
161 * @return int the section number
162 */
163 get_section_number: function(section) {
164 var sectionid = section.get('id').split('-');
165 if (sectionid.length < 2 || sectionid[0] != 'section') {
166 return false;
167 }
168 return parseInt(sectionid[1]);
169 },
170
171 /**
172 * Check if the event includes data of the given type
173 * @param e the event details
174 * @param type the data type to check for
175 * @return true if the data type is found in the event data
176 */
177 types_includes: function(e, type) {
178 var i;
a0a06d05
SH
179 if (e._event.dataTransfer === null) {
180 // TODO MDL-33054: If we get here then something has gone wrong.
181 return false;
182 }
32528f94 183 var types = e._event.dataTransfer.types;
abed5d65
DS
184 if (types == null) {
185 return false;
186 }
32528f94
DS
187 for (i=0; i<types.length; i++) {
188 if (types[i] == type) {
189 return true;
190 }
191 }
192 return false;
193 },
194
195 /**
196 * Look through the event data, checking it against the registered data types
197 * (in order of priority) and return details of the first matching data type
198 * @param e the event details
199 * @return mixed false if not found or an object {
200 * realtype: the type as given by the browser
201 * addmessage: the message to show to the user during dragging
202 * namemessage: the message for requesting a name for the resource from the user
203 * type: the identifier of the type (may match several 'realtype's)
204 * }
205 */
206 drag_type: function(e) {
207 if (this.types_includes(e, 'Files')) {
5103b5e6
DS
208 if (this.handlers.filehandlers.length == 0) {
209 return false; // No available file handlers - ignore this drag.
210 }
32528f94
DS
211 return {
212 realtype: 'Files',
33b24bdd 213 addmessage: M.util.get_string('addfilehere', 'moodle'),
32528f94
DS
214 namemessage: null, // Should not be asked for anyway
215 type: 'Files'
216 };
217 }
218
219 // Check each of the registered types
220 var types = this.handlers.types;
221 for (var i=0; i<types.length; i++) {
222 // Check each of the different identifiers for this type
223 var dttypes = types[i].datatransfertypes;
224 for (var j=0; j<dttypes.length; j++) {
225 if (this.types_includes(e, dttypes[j])) {
226 return {
227 realtype: dttypes[j],
228 addmessage: types[i].addmessage,
229 namemessage: types[i].namemessage,
230 type: types[i].identifier,
231 handlers: types[i].handlers
232 };
233 }
234 }
235 }
236 return false; // No types we can handle
237 },
238
239 /**
240 * Check the content of the drag/drop includes a type we can handle, then, if
241 * it is, notify the browser that we want to handle it
242 * @param event e
243 * @return string type of the event or false
244 */
245 check_drag: function(e) {
246 var type = this.drag_type(e);
247 if (type) {
248 // Notify browser that we will handle this drag/drop
249 e.stopPropagation();
250 e.preventDefault();
251 }
252 return type;
253 },
254
255 /**
256 * Handle a dragenter event: add a suitable 'add here' message
257 * when a drag event occurs, containing a registered data type
258 * @param e event data
259 * @return false to prevent the event from continuing to be processed
260 */
261 drag_enter: function(e) {
262 if (!(type = this.check_drag(e))) {
263 return false;
264 }
265
266 var section = this.get_section(e.currentTarget);
267 if (!section) {
268 return false;
269 }
270
271 if (this.currentsection && this.currentsection != section) {
272 this.currentsection = section;
273 this.entercount = 1;
274 } else {
275 this.entercount++;
276 if (this.entercount > 2) {
277 this.entercount = 2;
278 return false;
279 }
280 }
281
282 this.show_preview_element(section, type);
283
284 return false;
285 },
286
287 /**
288 * Handle a dragleave event: remove the 'add here' message (if present)
289 * @param e event data
290 * @return false to prevent the event from continuing to be processed
291 */
292 drag_leave: function(e) {
293 if (!this.check_drag(e)) {
294 return false;
295 }
296
297 this.entercount--;
298 if (this.entercount == 1) {
299 return false;
300 }
301 this.entercount = 0;
302 this.currentsection = null;
303
304 this.hide_preview_element();
305 return false;
306 },
307
308 /**
309 * Handle a dragover event: just prevent the browser default (necessary
310 * to allow drag and drop handling to work)
311 * @param e event data
312 * @return false to prevent the event from continuing to be processed
313 */
314 drag_over: function(e) {
315 this.check_drag(e);
316 return false;
317 },
318
319 /**
320 * Handle a drop event: hide the 'add here' message, check the attached
321 * data type and start the upload process
322 * @param e event data
323 * @return false to prevent the event from continuing to be processed
324 */
325 drop: function(e) {
326 if (!(type = this.check_drag(e))) {
327 return false;
328 }
329
330 this.hide_preview_element();
331
332 // Work out the number of the section we are on (from its id)
333 var section = this.get_section(e.currentTarget);
334 var sectionnumber = this.get_section_number(section);
335
336 // Process the file or the included data
337 if (type.type == 'Files') {
338 var files = e._event.dataTransfer.files;
339 for (var i=0, f; f=files[i]; i++) {
340 this.handle_file(f, section, sectionnumber);
341 }
342 } else {
343 var contents = e._event.dataTransfer.getData(type.realtype);
344 if (contents) {
345 this.handle_item(type, contents, section, sectionnumber);
346 }
347 }
348
349 return false;
350 },
351
352 /**
353 * Find or create the 'ul' element that contains all of the module
354 * instances in this section
355 * @param section the DOM element representing the section
356 * @return false to prevent the event from continuing to be processed
357 */
358 get_mods_element: function(section) {
359 // Find the 'ul' containing the list of mods
360 var modsel = section.one(this.modslistselector);
361 if (!modsel) {
362 // Create the above 'ul' if it doesn't exist
363 var modsel = document.createElement('ul');
364 modsel.className = 'section img-text';
365 var contentel = section.get('children').pop();
366 var brel = contentel.get('children').pop();
367 contentel.insertBefore(modsel, brel);
368 modsel = this.Y.one(modsel);
369 }
370
371 return modsel;
372 },
373
374 /**
375 * Add a new dummy item to the list of mods, to be replaced by a real
376 * item & link once the AJAX upload call has completed
377 * @param name the label to show in the element
378 * @param section the DOM element reperesenting the course section
379 * @return DOM element containing the new item
380 */
381 add_resource_element: function(name, section) {
382 var modsel = this.get_mods_element(section);
383
384 var resel = {
385 parent: modsel,
386 li: document.createElement('li'),
387 div: document.createElement('div'),
388 a: document.createElement('a'),
389 icon: document.createElement('img'),
390 namespan: document.createElement('span'),
391 progressouter: document.createElement('span'),
392 progress: document.createElement('span')
393 };
394
395 resel.li.className = 'activity resource modtype_resource';
396
397 resel.div.className = 'mod-indent';
398 resel.li.appendChild(resel.div);
399
400 resel.a.href = '#';
401 resel.div.appendChild(resel.a);
402
403 resel.icon.src = M.util.image_url('i/ajaxloader');
404 resel.a.appendChild(resel.icon);
405
406 resel.a.appendChild(document.createTextNode(' '));
407
408 resel.namespan.className = 'instancename';
409 resel.namespan.innerHTML = name;
410 resel.a.appendChild(resel.namespan);
411
412 resel.div.appendChild(document.createTextNode(' '));
413
414 resel.progressouter.className = 'dndupload-progress-outer';
415 resel.progress.className = 'dndupload-progress-inner';
416 resel.progress.innerHTML = '&nbsp;';
417 resel.progressouter.appendChild(resel.progress);
418 resel.div.appendChild(resel.progressouter);
419
420 modsel.insertBefore(resel.li, modsel.get('children').pop()); // Leave the 'preview element' at the bottom
421
422 return resel;
423 },
424
425 /**
426 * Hide any visible dndupload-preview elements on the page
427 */
428 hide_preview_element: function() {
429 this.Y.all('li.dndupload-preview').addClass('dndupload-hidden');
430 },
431
432 /**
433 * Unhide the preview element for the given section and set it to display
434 * the correct message
435 * @param section the YUI node representing the selected course section
436 * @param type the details of the data type detected in the drag (including the message to display)
437 */
438 show_preview_element: function(section, type) {
439 this.hide_preview_element();
440 var preview = section.one('li.dndupload-preview').removeClass('dndupload-hidden');
441 preview.one('span').setContent(type.addmessage);
442 },
443
444 /**
445 * Add the preview element to a course section. Note: this needs to be done before 'addEventListener'
446 * is called, otherwise Firefox will ignore events generated when the mouse is over the preview
447 * element (instead of passing them up to the parent element)
448 * @param section the YUI node representing the selected course section
449 */
450 add_preview_element: function(section) {
451 var modsel = this.get_mods_element(section);
452 var preview = {
453 li: document.createElement('li'),
454 div: document.createElement('div'),
455 icon: document.createElement('img'),
456 namespan: document.createElement('span')
457 };
458
459 preview.li.className = 'dndupload-preview activity resource modtype_resource dndupload-hidden';
460
461 preview.div.className = 'mod-indent';
462 preview.li.appendChild(preview.div);
463
464 preview.icon.src = M.util.image_url('t/addfile');
465 preview.div.appendChild(preview.icon);
466
467 preview.div.appendChild(document.createTextNode(' '));
468
469 preview.namespan.className = 'instancename';
33b24bdd 470 preview.namespan.innerHTML = M.util.get_string('addfilehere', 'moodle');
32528f94
DS
471 preview.div.appendChild(preview.namespan);
472
473 modsel.appendChild(preview.li);
474 },
475
476 /**
477 * Find the registered handler for the given file type. If there is more than one, ask the
478 * user which one to use. Then upload the file to the server
479 * @param file the details of the file, taken from the FileList in the drop event
480 * @param section the DOM element representing the selected course section
481 * @param sectionnumber the number of the selected course section
482 */
483 handle_file: function(file, section, sectionnumber) {
484 var handlers = new Array();
485 var filehandlers = this.handlers.filehandlers;
486 var extension = '';
487 var dotpos = file.name.lastIndexOf('.');
488 if (dotpos != -1) {
489 extension = file.name.substr(dotpos+1, file.name.length);
490 }
491
492 for (var i=0; i<filehandlers.length; i++) {
493 if (filehandlers[i].extension == '*' || filehandlers[i].extension == extension) {
494 handlers.push(filehandlers[i]);
495 }
496 }
497
498 if (handlers.length == 0) {
499 // No handlers at all (not even 'resource'?)
500 return;
501 }
502
503 if (handlers.length == 1) {
504 this.upload_file(file, section, sectionnumber, handlers[0].module);
505 return;
506 }
507
508 this.file_handler_dialog(handlers, extension, file, section, sectionnumber);
509 },
510
511 /**
512 * Show a dialog box, allowing the user to choose what to do with the file they are uploading
513 * @param handlers the available handlers to choose between
514 * @param extension the extension of the file being uploaded
515 * @param file the File object being uploaded
516 * @param section the DOM element of the section being uploaded to
517 * @param sectionnumber the number of the selected course section
518 */
519 file_handler_dialog: function(handlers, extension, file, section, sectionnumber) {
520 if (this.uploaddialog) {
521 var details = new Object();
522 details.isfile = true;
523 details.handlers = handlers;
524 details.extension = extension;
525 details.file = file;
526 details.section = section;
527 details.sectionnumber = sectionnumber;
528 this.uploadqueue.push(details);
529 return;
530 }
531 this.uploaddialog = true;
532
533 var timestamp = new Date().getTime();
534 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
535 var content = '';
536 var sel;
537 if (extension in this.lastselected) {
538 sel = this.lastselected[extension];
539 } else {
540 sel = handlers[0].module;
541 }
33b24bdd 542 content += '<p>'+M.util.get_string('actionchoice', 'moodle', file.name)+'</p>';
32528f94
DS
543 content += '<div id="dndupload_handlers'+uploadid+'">';
544 for (var i=0; i<handlers.length; i++) {
545 var id = 'dndupload_handler'+uploadid+handlers[i].module;
546 var checked = (handlers[i].module == sel) ? 'checked="checked" ' : '';
547 content += '<input type="radio" name="handler" value="'+handlers[i].module+'" id="'+id+'" '+checked+'/>';
548 content += ' <label for="'+id+'">';
549 content += handlers[i].message;
550 content += '</label><br/>';
551 }
552 content += '</div>';
553
554 var Y = this.Y;
555 var self = this;
556 var panel = new Y.Panel({
557 bodyContent: content,
558 width: 350,
559 zIndex: 5,
560 centered: true,
561 modal: true,
562 visible: true,
563 render: true,
564 buttons: [{
a0a06d05 565 value: M.util.get_string('upload', 'moodle'),
32528f94
DS
566 action: function(e) {
567 e.preventDefault();
568 // Find out which module was selected
569 var module = false;
570 var div = Y.one('#dndupload_handlers'+uploadid);
571 div.all('input').each(function(input) {
572 if (input.get('checked')) {
573 module = input.get('value');
574 }
575 });
576 if (!module) {
577 return;
578 }
579 panel.hide();
580 // Remember this selection for next time
581 self.lastselected[extension] = module;
582 // Do the upload
583 self.upload_file(file, section, sectionnumber, module);
584 },
585 section: Y.WidgetStdMod.FOOTER
586 },{
a0a06d05 587 value: M.util.get_string('cancel', 'moodle'),
32528f94
DS
588 action: function(e) {
589 e.preventDefault();
590 panel.hide();
591 },
592 section: Y.WidgetStdMod.FOOTER
593 }]
594 });
595 // When the panel is hidden - destroy it and then check for other pending uploads
596 panel.after("visibleChange", function(e) {
597 if (!panel.get('visible')) {
598 panel.destroy(true);
599 self.check_upload_queue();
600 }
601 });
602 },
603
604 /**
605 * Check to see if there are any other dialog boxes to show, now that the current one has
606 * been dealt with
607 */
608 check_upload_queue: function() {
609 this.uploaddialog = false;
610 if (this.uploadqueue.length == 0) {
611 return;
612 }
613
614 var details = this.uploadqueue.shift();
615 if (details.isfile) {
616 this.file_handler_dialog(details.handlers, details.extension, details.file, details.section, details.sectionnumber);
617 } else {
618 this.handle_item(details.type, details.contents, details.section, details.sectionnumber);
619 }
620 },
621
622 /**
623 * Do the file upload: show the dummy element, use an AJAX call to send the data
624 * to the server, update the progress bar for the file, then replace the dummy
625 * element with the real information once the AJAX call completes
626 * @param file the details of the file, taken from the FileList in the drop event
627 * @param section the DOM element representing the selected course section
628 * @param sectionnumber the number of the selected course section
629 */
630 upload_file: function(file, section, sectionnumber, module) {
631
632 // This would be an ideal place to use the Y.io function
633 // however, this does not support data encoded using the
634 // FormData object, which is needed to transfer data from
635 // the DataTransfer object into an XMLHTTPRequest
636 // This can be converted when the YUI issue has been integrated:
637 // http://yuilibrary.com/projects/yui3/ticket/2531274
638 var xhr = new XMLHttpRequest();
639 var self = this;
640
641 if (file.size > this.maxbytes) {
33b24bdd 642 alert("'"+file.name+"' "+M.util.get_string('filetoolarge', 'moodle'));
32528f94
DS
643 return;
644 }
645
646 // Add the file to the display
647 var resel = this.add_resource_element(file.name, section);
648
649 // Update the progress bar as the file is uploaded
650 xhr.upload.addEventListener('progress', function(e) {
651 if (e.lengthComputable) {
652 var percentage = Math.round((e.loaded * 100) / e.total);
653 resel.progress.style.width = percentage + '%';
654 }
655 }, false);
656
657 // Wait for the AJAX call to complete, then update the
658 // dummy element with the returned details
659 xhr.onreadystatechange = function() {
660 if (xhr.readyState == 4) {
661 if (xhr.status == 200) {
662 var result = JSON.parse(xhr.responseText);
663 if (result) {
664 if (result.error == 0) {
665 // All OK - update the dummy element
666 resel.icon.src = result.icon;
667 resel.a.href = result.link;
668 resel.namespan.innerHTML = result.name;
669 resel.div.removeChild(resel.progressouter);
670 resel.li.id = result.elementid;
671 resel.div.innerHTML += result.commands;
672 if (result.onclick) {
673 resel.a.onclick = result.onclick;
674 }
675 self.add_editing(result.elementid);
676 } else {
677 // Error - remove the dummy element
678 resel.parent.removeChild(resel.li);
679 alert(result.error);
680 }
681 }
682 } else {
33b24bdd 683 alert(M.util.get_string('servererror', 'moodle'));
32528f94
DS
684 }
685 }
686 };
687
688 // Prepare the data to send
689 var formData = new FormData();
690 formData.append('repo_upload_file', file);
691 formData.append('sesskey', M.cfg.sesskey);
692 formData.append('course', this.courseid);
693 formData.append('section', sectionnumber);
694 formData.append('module', module);
695 formData.append('type', 'Files');
696
697 // Send the AJAX call
698 xhr.open("POST", this.url, true);
699 xhr.send(formData);
700 },
701
702 /**
703 * Show a dialog box to gather the name of the resource / activity to be created
704 * from the uploaded content
705 * @param type the details of the type of content
706 * @param contents the contents to be uploaded
707 * @section the DOM element for the section being uploaded to
708 * @sectionnumber the number of the section being uploaded to
709 */
710 handle_item: function(type, contents, section, sectionnumber) {
711 if (type.handlers.length == 0) {
712 // Nothing to handle this - should not have got here
713 return;
714 }
715
716 if (this.uploaddialog) {
717 var details = new Object();
718 details.isfile = false;
719 details.type = type;
720 details.contents = contents;
721 details.section = section;
722 details.setcionnumber = sectionnumber;
723 this.uploadqueue.push(details);
724 return;
725 }
726 this.uploaddialog = true;
727
728 var timestamp = new Date().getTime();
729 var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
730 var nameid = 'dndupload_handler_name'+uploadid;
731 var content = '';
732 content += '<label for="'+nameid+'">'+type.namemessage+'</label>';
733 content += ' <input type="text" id="'+nameid+'" value="" />';
734 if (type.handlers.length > 1) {
735 content += '<div id="dndupload_handlers'+uploadid+'">';
736 var sel = type.handlers[0].module;
737 for (var i=0; i<type.handlers.length; i++) {
738 var id = 'dndupload_handler'+uploadid;
739 var checked = (type.handlers[i].module == sel) ? 'checked="checked" ' : '';
740 content += '<input type="radio" name="handler" value="'+type.handlers[i].module+'" id="'+id+'" '+checked+'/>';
741 content += ' <label for="'+id+'">';
742 content += type.handlers[i].message;
743 content += '</label><br/>';
744 }
745 content += '</div>';
746 }
747
748 var Y = this.Y;
749 var self = this;
750 var panel = new Y.Panel({
751 bodyContent: content,
752 width: 350,
753 zIndex: 5,
754 centered: true,
755 modal: true,
756 visible: true,
757 render: true,
758 buttons: [{
a0a06d05 759 value: M.util.get_string('upload', 'moodle'),
32528f94
DS
760 action: function(e) {
761 e.preventDefault();
762 var name = Y.one('#dndupload_handler_name'+uploadid).get('value');
763 name = name.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); // Trim
764 if (name == '') {
765 return;
766 }
767 var module = false;
768 if (type.handlers.length > 1) {
769 // Find out which module was selected
770 var div = Y.one('#dndupload_handlers'+uploadid);
771 div.all('input').each(function(input) {
772 if (input.get('checked')) {
773 module = input.get('value');
774 }
775 });
776 if (!module) {
777 return;
778 }
779 } else {
780 module = type.handlers[0].module;
781 }
782 panel.hide();
783 // Do the upload
784 self.upload_item(name, type.type, contents, section, sectionnumber, module);
785 },
786 section: Y.WidgetStdMod.FOOTER
787 },{
a0a06d05 788 value: M.util.get_string('cancel', 'moodle'),
32528f94
DS
789 action: function(e) {
790 e.preventDefault();
791 panel.hide();
792 },
793 section: Y.WidgetStdMod.FOOTER
794 }]
795 });
796 // When the panel is hidden - destroy it and then check for other pending uploads
797 panel.after("visibleChange", function(e) {
798 if (!panel.get('visible')) {
799 panel.destroy(true);
800 self.check_upload_queue();
801 }
802 });
803 // Focus on the 'name' box
804 Y.one('#'+nameid).focus();
805 },
806
807 /**
808 * Upload any data types that are not files: display a dummy resource element, send
809 * the data to the server, update the progress bar for the file, then replace the
810 * dummy element with the real information once the AJAX call completes
811 * @param name the display name for the resource / activity to create
812 * @param type the details of the data type found in the drop event
813 * @param contents the actual data that was dropped
814 * @param section the DOM element representing the selected course section
815 * @param sectionnumber the number of the selected course section
816 */
817 upload_item: function(name, type, contents, section, sectionnumber, module) {
818
819 // This would be an ideal place to use the Y.io function
820 // however, this does not support data encoded using the
821 // FormData object, which is needed to transfer data from
822 // the DataTransfer object into an XMLHTTPRequest
823 // This can be converted when the YUI issue has been integrated:
824 // http://yuilibrary.com/projects/yui3/ticket/2531274
825 var xhr = new XMLHttpRequest();
826 var self = this;
827
828 // Add the item to the display
829 var resel = this.add_resource_element(name, section);
830
831 // Wait for the AJAX call to complete, then update the
832 // dummy element with the returned details
833 xhr.onreadystatechange = function() {
834 if (xhr.readyState == 4) {
835 if (xhr.status == 200) {
836 var result = JSON.parse(xhr.responseText);
837 if (result) {
838 if (result.error == 0) {
839 // All OK - update the dummy element
840 resel.icon.src = result.icon;
841 resel.a.href = result.link;
842 resel.namespan.innerHTML = result.name;
843 resel.div.removeChild(resel.progressouter);
844 resel.li.id = result.elementid;
845 resel.div.innerHTML += result.commands;
846 if (result.onclick) {
847 resel.a.onclick = result.onclick;
848 }
849 self.add_editing(result.elementid, sectionnumber);
850 } else {
851 // Error - remove the dummy element
852 resel.parent.removeChild(resel.li);
853 alert(result.error);
854 }
855 }
856 } else {
33b24bdd 857 alert(M.util.get_string('servererror', 'moodle'));
32528f94
DS
858 }
859 }
860 };
861
862 // Prepare the data to send
863 var formData = new FormData();
864 formData.append('contents', contents);
865 formData.append('displayname', name);
866 formData.append('sesskey', M.cfg.sesskey);
867 formData.append('course', this.courseid);
868 formData.append('section', sectionnumber);
869 formData.append('type', type);
870 formData.append('module', module);
871
872 // Send the data
873 xhr.open("POST", this.url, true);
874 xhr.send(formData);
875 },
876
877 /**
878 * Call the AJAX course editing initialisation to add the editing tools
879 * to the newly-created resource link
880 * @param elementid the id of the DOM element containing the new resource link
881 * @param sectionnumber the number of the selected course section
882 */
883 add_editing: function(elementid) {
884 YUI().use('moodle-course-coursebase', function(Y) {
885 M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);
886 });
887 }
888};