Skip to content
Browse files

MDL-31321 Drag and drop upload - can now cope with multiple filemanag…

…er/filepickers on a page
  • Loading branch information...
1 parent baa5cd8 commit 6d9121d3a860ea070f15b10edc89ed11f62c77ae @davosmith davosmith committed Feb 7, 2012
Showing with 346 additions and 340 deletions.
  1. +346 −340 lib/form/dndupload.js
View
686 lib/form/dndupload.js
@@ -22,380 +22,386 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-M.form_dndupload = {
- // YUI object.
- Y: null,
- // URL for upload requests
- url: M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload',
- // itemid used for repository upload
- itemid: null,
- // accepted filetypes accepted by this form passed to repository
- acceptedtypes: [],
- // maximum number of files this form allows
- maxfiles: 0,
- // maximum size of files allowed in this form
- maxbytes: 0,
- // unqiue id of this form field used for html elements
- clientid: '',
- // upload repository id, used for upload
- repositoryid: 0,
- // container which holds the node which recieves drag events
- container: null,
- // filemanager element we are working with
- filemanager: null,
- // callback to filepicker element to refesh when uploaded
- callback: null,
- // Nasty hack to distinguish between dragenter(first entry),
- // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
- entercount: 0,
-
-
- /**
- * Initalise the drag and drop upload interface
- * Note: one and only one of options.filemanager and options.formcallback must be defined
- *
- * @param Y the YUI object
- * @param object options {
- * itemid: itemid used for repository upload in this form
- * acceptdtypes: accepted filetypes by this form
- * maxfiles: maximum number of files this form allows
- * maxbytes: maximum size of files allowed in this form
- * clientid: unqiue id of this form field used for html elements
- * containerprefix: prefix of htmlid of container
- * repositories: array of repository objects passed from filepicker
- * filemanager: filemanager element we are working with
- * callback: callback to filepicker element to refesh when uploaded
- * }
- */
- init: function(Y, options) {
- this.Y = Y;
-
- if (!this.browser_supported()) {
- return; // Browser does not support the required functionality
- }
-
- // try and retrieve enabled upload repository
- this.repositoryid = this.get_upload_repositoryid(options.repositories);
-
- if (!this.repositoryid) {
- return; // no upload repository is enabled to upload to
- }
-
- this.acceptedtypes = options.acceptedtypes;
- this.clientid = options.clientid;
- this.maxfiles = options.maxfiles;
- this.maxbytes = options.maxbytes;
- this.itemid = options.itemid;
- this.container = this.Y.one(options.containerprefix + this.clientid);
-
- if (options.filemanager) {
- // Needed to tell the filemanager to redraw when files uploaded
- // and to check how many files are already uploaded
- this.filemanager = options.filemanager;
- } else if (options.formcallback) {
-
- // Needed to tell the filepicker to update when a new
- // file is uploaded
- this.callback = options.formcallback;
- } else {
- if (M.cfg.developerdebug) {
- alert('dndupload: Need to define either options.filemanager or options.callback');
+M.form_dndupload = {}
+
+M.form_dndupload.init = function(Y, options) {
+ var dnduploadhelper = {
+ // YUI object.
+ Y: null,
+ // URL for upload requests
+ url: M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload',
+ // itemid used for repository upload
+ itemid: null,
+ // accepted filetypes accepted by this form passed to repository
+ acceptedtypes: [],
+ // maximum number of files this form allows
+ maxfiles: 0,
+ // maximum size of files allowed in this form
+ maxbytes: 0,
+ // unqiue id of this form field used for html elements
+ clientid: '',
+ // upload repository id, used for upload
+ repositoryid: 0,
+ // container which holds the node which recieves drag events
+ container: null,
+ // filemanager element we are working with
+ filemanager: null,
+ // callback to filepicker element to refesh when uploaded
+ callback: null,
+ // Nasty hack to distinguish between dragenter(first entry),
+ // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
+ entercount: 0,
+
+
+ /**
+ * Initalise the drag and drop upload interface
+ * Note: one and only one of options.filemanager and options.formcallback must be defined
+ *
+ * @param Y the YUI object
+ * @param object options {
+ * itemid: itemid used for repository upload in this form
+ * acceptdtypes: accepted filetypes by this form
+ * maxfiles: maximum number of files this form allows
+ * maxbytes: maximum size of files allowed in this form
+ * clientid: unqiue id of this form field used for html elements
+ * containerprefix: prefix of htmlid of container
+ * repositories: array of repository objects passed from filepicker
+ * filemanager: filemanager element we are working with
+ * callback: callback to filepicker element to refesh when uploaded
+ * }
+ */
+ init: function(Y, options) {
+ this.Y = Y;
+
+ if (!this.browser_supported()) {
+ return; // Browser does not support the required functionality
}
- return;
- }
-
- this.init_events();
- this.Y.one('#dndenabled-'+this.clientid).setStyle('display', 'inline');
- },
- /**
- * Check the browser has the required functionality
- * @return true if browser supports drag/drop upload
- */
- browser_supported: function() {
+ // try and retrieve enabled upload repository
+ this.repositoryid = this.get_upload_repositoryid(options.repositories);
- if (typeof FileReader == 'undefined') {
- return false;
- }
- if (typeof FormData == 'undefined') {
- return false;
- }
- return true;
- },
-
- /**
- * Get upload repoistory from array of enabled repositories
- *
- * @param array repositories repository objects passed from filepicker
- * @param returns int id of upload repository or false if not found
- */
- get_upload_repositoryid: function(repositories) {
- for (var i in repositories) {
- if (repositories[i].type == "upload") {
- return repositories[i].id;
- }
- }
-
- return false;
- },
-
- /**
- * Initialise drag events on node container, all events need
- * to be processed for drag and drop to work
- */
- init_events: function() {
- this.Y.on('dragenter', this.drag_enter, this.container, this);
- this.Y.on('dragleave', this.drag_leave, this.container, this);
- this.Y.on('dragover', this.drag_over, this.container, this);
- this.Y.on('drop', this.drop, this.container, this);
- },
-
- /**
- * Check if the drag contents are valid and then call
- * preventdefault / stoppropagation to let the browser know
- * we will handle this drag/drop
- *
- * @param e event object
- * @return boolean true if a valid file drag event
- */
- check_drag: function(e) {
- if (!this.has_files(e)) {
- return false;
- }
+ if (!this.repositoryid) {
+ return; // no upload repository is enabled to upload to
+ }
- e.preventDefault();
- e.stopPropagation();
+ this.acceptedtypes = options.acceptedtypes;
+ this.clientid = options.clientid;
+ this.maxfiles = options.maxfiles;
+ this.maxbytes = options.maxbytes;
+ this.itemid = options.itemid;
+ this.container = this.Y.one(options.containerprefix + this.clientid);
+
+ if (options.filemanager) {
+ // Needed to tell the filemanager to redraw when files uploaded
+ // and to check how many files are already uploaded
+ this.filemanager = options.filemanager;
+ } else if (options.formcallback) {
+
+ // Needed to tell the filepicker to update when a new
+ // file is uploaded
+ this.callback = options.formcallback;
+ } else {
+ if (M.cfg.developerdebug) {
+ alert('dndupload: Need to define either options.filemanager or options.callback');
+ }
+ return;
+ }
- if (this.reached_maxfiles()) {
- return false;
- }
+ this.init_events();
+ this.Y.one('#dndenabled-'+this.clientid).setStyle('display', 'inline');
+ },
- return true;
- },
+ /**
+ * Check the browser has the required functionality
+ * @return true if browser supports drag/drop upload
+ */
+ browser_supported: function() {
- /**
- * Handle a dragenter event, highlight the destination node
- * when a suitable drag event occurs
- */
- drag_enter: function(e) {
- if (!this.check_drag(e)) {
+ if (typeof FileReader == 'undefined') {
+ return false;
+ }
+ if (typeof FormData == 'undefined') {
+ return false;
+ }
return true;
- }
+ },
+
+ /**
+ * Get upload repoistory from array of enabled repositories
+ *
+ * @param array repositories repository objects passed from filepicker
+ * @param returns int id of upload repository or false if not found
+ */
+ get_upload_repositoryid: function(repositories) {
+ for (var i in repositories) {
+ if (repositories[i].type == "upload") {
+ return repositories[i].id;
+ }
+ }
- this.entercount++;
- if (this.entercount >= 2) {
- this.entercount = 2; // Just moved over a child element - nothing to do
return false;
- }
+ },
+
+ /**
+ * Initialise drag events on node container, all events need
+ * to be processed for drag and drop to work
+ */
+ init_events: function() {
+ this.Y.on('dragenter', this.drag_enter, this.container, this);
+ this.Y.on('dragleave', this.drag_leave, this.container, this);
+ this.Y.on('dragover', this.drag_over, this.container, this);
+ this.Y.on('drop', this.drop, this.container, this);
+ },
+
+ /**
+ * Check if the drag contents are valid and then call
+ * preventdefault / stoppropagation to let the browser know
+ * we will handle this drag/drop
+ *
+ * @param e event object
+ * @return boolean true if a valid file drag event
+ */
+ check_drag: function(e) {
+ if (!this.has_files(e)) {
+ return false;
+ }
- this.show_upload_ready();
- return false;
- },
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (this.reached_maxfiles()) {
+ return false;
+ }
- /**
- * Handle a dragleave event, Remove the highlight if dragged from
- * node
- */
- drag_leave: function(e) {
- if (!this.check_drag(e)) {
return true;
- }
+ },
+
+ /**
+ * Handle a dragenter event, highlight the destination node
+ * when a suitable drag event occurs
+ */
+ drag_enter: function(e) {
+ if (!this.check_drag(e)) {
+ return true;
+ }
- this.entercount--;
- if (this.entercount == 1) {
- return false; // Just moved over a child element - nothing to do
- }
+ this.entercount++;
+ if (this.entercount >= 2) {
+ this.entercount = 2; // Just moved over a child element - nothing to do
+ return false;
+ }
- this.entercount = 0;
- this.hide_upload_ready();
- return false;
- },
-
- /**
- * Handle a dragover event. Required to intercept to prevent the browser from
- * handling the drag and drop event as normal
- */
- drag_over: function(e) {
- if (!this.check_drag(e)) {
- return true;
- }
+ this.show_upload_ready();
+ return false;
+ },
+
+ /**
+ * Handle a dragleave event, Remove the highlight if dragged from
+ * node
+ */
+ drag_leave: function(e) {
+ if (!this.check_drag(e)) {
+ return true;
+ }
- return false;
- },
+ this.entercount--;
+ if (this.entercount == 1) {
+ return false; // Just moved over a child element - nothing to do
+ }
- /**
- * Handle a drop event. Remove the highlight and then upload each
- * of the files (until we reach the file limit, or run out of files)
- */
- drop: function(e) {
- if (!this.check_drag(e)) {
- return true;
- }
+ this.entercount = 0;
+ this.hide_upload_ready();
+ return false;
+ },
+
+ /**
+ * Handle a dragover event. Required to intercept to prevent the browser from
+ * handling the drag and drop event as normal
+ */
+ drag_over: function(e) {
+ if (!this.check_drag(e)) {
+ return true;
+ }
- this.entercount = 0;
- this.hide_upload_ready();
- this.show_progress_spinner();
+ return false;
+ },
+
+ /**
+ * Handle a drop event. Remove the highlight and then upload each
+ * of the files (until we reach the file limit, or run out of files)
+ */
+ drop: function(e) {
+ if (!this.check_drag(e)) {
+ return true;
+ }
+
+ this.entercount = 0;
+ this.hide_upload_ready();
+ this.show_progress_spinner();
- var files = e._event.dataTransfer.files;
- if (this.filemanager) {
- var currentfilecount = this.filemanager.filecount;
- for (var i=0, f; f=files[i]; i++) {
- if (currentfilecount >= this.maxfiles && this.maxfiles != -1) {
- break;
+ var files = e._event.dataTransfer.files;
+ if (this.filemanager) {
+ var currentfilecount = this.filemanager.filecount;
+ for (var i=0, f; f=files[i]; i++) {
+ if (currentfilecount >= this.maxfiles && this.maxfiles != -1) {
+ break;
+ }
+ if (this.upload_file(f)) {
+ currentfilecount++;
+ }
}
- if (this.upload_file(f)) {
- currentfilecount++;
+ } else {
+ if (files.length >= 1) {
+ this.upload_file(files[0]);
}
}
- } else {
- if (files.length >= 1) {
- this.upload_file(files[0]);
- }
- }
- return false;
- },
-
- /**
- * Check to see if the drag event has any files in it
- *
- * @param e event object
- * @return boolean true if event has files
- */
- has_files: function(e) {
- var types = e._event.dataTransfer.types;
- for (var i=0; i<types.length; i++) {
- if (types[i] == 'Files') {
- return true;
+ return false;
+ },
+
+ /**
+ * Check to see if the drag event has any files in it
+ *
+ * @param e event object
+ * @return boolean true if event has files
+ */
+ has_files: function(e) {
+ var types = e._event.dataTransfer.types;
+ for (var i=0; i<types.length; i++) {
+ if (types[i] == 'Files') {
+ return true;
+ }
}
- }
- return false;
- },
-
- /**
- * Check if reached the maximumum number of allowed files
- *
- * @return boolean true if reached maximum number of files
- */
- reached_maxfiles: function() {
- if (this.filemanager) {
- if (this.filemanager.filecount >= this.maxfiles && this.maxfiles != -1) {
- return true;
+ return false;
+ },
+
+ /**
+ * Check if reached the maximumum number of allowed files
+ *
+ * @return boolean true if reached maximum number of files
+ */
+ reached_maxfiles: function() {
+ if (this.filemanager) {
+ if (this.filemanager.filecount >= this.maxfiles && this.maxfiles != -1) {
+ return true;
+ }
}
- }
- return false;
- },
-
- /**
- * Highlight the destination node
- */
- show_upload_ready: function() {
- this.container.addClass('dndupload-over');
- },
-
- /**
- * Remove highlight on destination node
- */
- hide_upload_ready: function() {
- this.container.removeClass('dndupload-over');
- },
-
- /**
- * Display a progress spinner in the destination node
- */
- show_progress_spinner: function() {
- // add a loading spinner to show something is happening
- var loadingspinner = this.Y.Node.create('<div id="dndprogresspinner-'+this.clientid+'" style="text-align: center">');
- loadingspinner.append('<img src="'+M.util.image_url('i/loading_small')+'" />');
- this.container.append(loadingspinner);
- },
-
- /**
- * Remove progress spinner in the destination node
- */
- hide_progress_spinner: function() {
- this.Y.one('#dndprogresspinner-'+this.clientid).remove();
- },
-
- /**
- * Tell the attached filemanager element (if any) to refresh on file
- * upload
- */
- update_filemanager: function() {
- if (this.filemanager) {
- // update the filemanager that we've uploaded the files
- this.filemanager.filepicker_callback();
- }
- },
-
- /**
- * Upload a single file via an AJAX call to the 'upload' repository
- */
- upload_file: function(file) {
- if (file.size > this.maxbytes && this.maxbytes > 0) {
- // Check filesize before attempting to upload
- this.hide_progress_spinner();
- alert(M.util.get_string('uploadformlimit', 'moodle')+"\n'"+file.name+"'");
return false;
- }
+ },
+
+ /**
+ * Highlight the destination node
+ */
+ show_upload_ready: function() {
+ this.container.addClass('dndupload-over');
+ },
+
+ /**
+ * Remove highlight on destination node
+ */
+ hide_upload_ready: function() {
+ this.container.removeClass('dndupload-over');
+ },
+
+ /**
+ * Display a progress spinner in the destination node
+ */
+ show_progress_spinner: function() {
+ // add a loading spinner to show something is happening
+ var loadingspinner = this.Y.Node.create('<div id="dndprogresspinner-'+this.clientid+'" style="text-align: center">');
+ loadingspinner.append('<img src="'+M.util.image_url('i/loading_small')+'" />');
+ this.container.append(loadingspinner);
+ },
+
+ /**
+ * Remove progress spinner in the destination node
+ */
+ hide_progress_spinner: function() {
+ this.Y.one('#dndprogresspinner-'+this.clientid).remove();
+ },
+
+ /**
+ * Tell the attached filemanager element (if any) to refresh on file
+ * upload
+ */
+ update_filemanager: function() {
+ if (this.filemanager) {
+ // update the filemanager that we've uploaded the files
+ this.filemanager.filepicker_callback();
+ }
+ },
+
+ /**
+ * Upload a single file via an AJAX call to the 'upload' repository
+ */
+ upload_file: function(file) {
+ if (file.size > this.maxbytes && this.maxbytes > 0) {
+ // Check filesize before attempting to upload
+ this.hide_progress_spinner();
+ alert(M.util.get_string('uploadformlimit', 'moodle')+"\n'"+file.name+"'");
+ return false;
+ }
- // This would be an ideal place to use the Y.io function
- // however, this does not support data encoded using the
- // FormData object, which is needed to transfer data from
- // the DataTransfer object into an XMLHTTPRequest
- // This can be converted when the YUI issue has been integrated:
- // http://yuilibrary.com/projects/yui3/ticket/2531274
- var xhr = new XMLHttpRequest();
- var self = this;
- xhr.onreadystatechange = function() { // Process the server response
- if (xhr.readyState == 4) {
- self.hide_progress_spinner();
- if (xhr.status == 200) {
- var result = JSON.parse(xhr.responseText);
- if (result) {
- if (result.error) {
- alert(result.error);
- } else if (self.callback) {
- // Only update the filepicker if there were no errors
- if (result.event == 'fileexists') {
- // Do not worry about this, as we only care about the last
- // file uploaded, with the filepicker
- result.file = result.newfile.filename;
- result.url = result.newfile.url;
+ // This would be an ideal place to use the Y.io function
+ // however, this does not support data encoded using the
+ // FormData object, which is needed to transfer data from
+ // the DataTransfer object into an XMLHTTPRequest
+ // This can be converted when the YUI issue has been integrated:
+ // http://yuilibrary.com/projects/yui3/ticket/2531274
+ var xhr = new XMLHttpRequest();
+ var self = this;
+ xhr.onreadystatechange = function() { // Process the server response
+ if (xhr.readyState == 4) {
+ self.hide_progress_spinner();
+ if (xhr.status == 200) {
+ var result = JSON.parse(xhr.responseText);
+ if (result) {
+ if (result.error) {
+ alert(result.error);
+ } else if (self.callback) {
+ // Only update the filepicker if there were no errors
+ if (result.event == 'fileexists') {
+ // Do not worry about this, as we only care about the last
+ // file uploaded, with the filepicker
+ result.file = result.newfile.filename;
+ result.url = result.newfile.url;
+ }
+ result.client_id = self.clientid;
+ self.callback(result);
+ } else {
+ self.update_filemanager();
}
- result.client_id = self.clientid;
- self.callback(result);
- } else {
- self.update_filemanager();
}
+ } else {
+ alert(M.util.get_string('serverconnection', 'error'));
}
- } else {
- alert(M.util.get_string('serverconnection', 'error'));
}
+ };
+
+ // Prepare the data to send
+ var formdata = new FormData();
+ formdata.append('repo_upload_file', file); // The FormData class allows us to attach a file
+ formdata.append('sesskey', M.cfg.sesskey);
+ formdata.append('repo_id', this.repositoryid);
+ formdata.append('itemid', this.itemid);
+ if (this.filemanager) { // Filepickers do not have folders
+ formdata.append('savepath', this.filemanager.currentpath);
}
- };
-
- // Prepare the data to send
- var formdata = new FormData();
- formdata.append('repo_upload_file', file); // The FormData class allows us to attach a file
- formdata.append('sesskey', M.cfg.sesskey);
- formdata.append('repo_id', this.repositoryid);
- formdata.append('itemid', this.itemid);
- if (this.filemanager) { // Filepickers do not have folders
- formdata.append('savepath', this.filemanager.currentpath);
- }
- if (this.acceptedtypes.constructor == Array) {
- for (var i=0; i<this.acceptedtypes.length; i++) {
- formdata.append('accepted_types[]', this.acceptedtypes[i]);
+ if (this.acceptedtypes.constructor == Array) {
+ for (var i=0; i<this.acceptedtypes.length; i++) {
+ formdata.append('accepted_types[]', this.acceptedtypes[i]);
+ }
+ } else {
+ formdata.append('accepted_types[]', this.acceptedtypes);
}
- } else {
- formdata.append('accepted_types[]', this.acceptedtypes);
+
+ // Send the file & required details
+ xhr.open("POST", this.url, true);
+ xhr.send(formdata);
+ return true;
}
+ };
- // Send the file & required details
- xhr.open("POST", this.url, true);
- xhr.send(formdata);
- return true;
- }
+ dnduploadhelper.init(Y, options);
};

0 comments on commit 6d9121d

Please sign in to comment.
Something went wrong with that request. Please try again.