Skip to content

Commit

Permalink
MDL-29766 Filemanager - now able to drag and drop files from the desk…
Browse files Browse the repository at this point in the history
…top onto a filemanager form element to upload them (with supported web browsers)
  • Loading branch information
davosmith committed Oct 12, 2011
1 parent 6731a04 commit e74580a
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 3 deletions.
2 changes: 2 additions & 0 deletions admin/settings/development.php
Expand Up @@ -17,6 +17,8 @@
$temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', get_string('enablesafebrowserintegration', 'admin'), get_string('configenablesafebrowserintegration', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('enablegroupmembersonly', get_string('enablegroupmembersonly', 'admin'), get_string('configenablegroupmembersonly', 'admin'), 0));

$temp->add(new admin_setting_configcheckbox('enabledndupload', get_string('enabledndupload', 'admin'), get_string('configenabledndupload', 'admin'), 0));

$ADMIN->add('experimental', $temp);

// "debugging" settingpage
Expand Down
2 changes: 2 additions & 0 deletions lang/en/admin.php
Expand Up @@ -190,6 +190,7 @@
$string['configenablecalendarexport'] = 'Enable exporting or subscribing to calendars.';
$string['configenablecomments'] = 'Enable comments';
$string['configenablecourserequests'] = 'This will allow any user to request a course be created.';
$string['configenabledndupload'] = 'Allows files to be dragged and dropped onto filemanager elements to upload them (requires a web browser that implements the relevant HTML 5 standards)';
$string['configenableglobalsearch'] = 'This setting enables global text searching in resources and activities, it is not compatible with PHP 4.';
$string['configenablegroupmembersonly'] = 'If enabled, access to activities can be restricted to group members only. This may result in an increased server load. In addition, gradebook categories must be set up in a certain way to ensure that activities are hidden from non-group members.';
$string['configenablehtmlpurifier'] = 'Use HTML Purifier instead of KSES for cleaning of untrusted text. HTML Purifier is actively developed and is believed to be more secure, but it is more resource intensive. Expect minor visual differences in the resulting html code. Please note that embed and object tags can not be enabled, MathML tags and old lang tags are not supported.';
Expand Down Expand Up @@ -467,6 +468,7 @@
$string['enablecourseajax_desc'] = 'Allow AJAX when editing main course pages. Note that the course format and the theme must support AJAX editing and the user has to enable AJAX in their profiles, too.';
$string['enablecourserequests'] = 'Enable course requests';
$string['enabledevicedetection'] = 'Enable device detection';
$string['enabledndupload'] = 'Enable drag and drop upload';
$string['enableglobalsearch'] = 'Enable global search';
$string['enablegravatar'] = 'Enable Gravatar';
$string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.';
Expand Down
2 changes: 2 additions & 0 deletions lang/en/moodle.php
Expand Up @@ -451,6 +451,8 @@
$string['displayingrecords'] = 'Displaying {$a} records';
$string['displayingusers'] = 'Displaying users {$a->start} to {$a->end}';
$string['displayonpage'] = 'Display on page';
$string['dndenabled'] = 'Drag and drop upload enabled';
$string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.<br />Note: this may not work with other web browsers';
$string['documentation'] = 'Moodle documentation';
$string['down'] = 'Down';
$string['download'] = 'Download';
Expand Down
213 changes: 212 additions & 1 deletion lib/form/filemanager.js
Expand Up @@ -763,5 +763,216 @@ M.form_filemanager.init = function(Y, options) {
item.style.display = '';
}

new FileManagerHelper(options);
options.filemanager = new FileManagerHelper(options);

var DndUploadHelper = function(options) {
DndUploadHelper.superclass.constructor.apply(this, arguments);
};
DndUploadHelper.NAME = "DndUpload";
DndUploadHelper.ATTRS = {
options: {},
lang: {}
};

Y.extend(DndUploadHelper, Y.Base, {
api: M.cfg.wwwroot+'/repository/repository_ajax.php',
initializer: function(options) {
if (!options.enabledndupload) {
return;
}

if (!this.browsersupport()) {
return;
}

this.options = options;
this.client_id = options.client_id;
this.maxfiles = options.maxfiles;
this.maxbytes = options.maxbytes;
this.itemid = options.itemid;

this.upload_repo = options.upload_repo;

this.uploadingcount = 0;

this.container = Y.one('#filemanager-'+this.client_id);

// Needed to tell the filemanager to redraw when files uploaded
// and to check how many files are already uploaded
this.filemanager = options.filemanager;

// Nasty hack to distinguish between dragenter(first entry), dragenter+dragleave(moving between child elements) and dragleave (leaving element)
this.entercount = 0;

this.initevents();
this.initinstructions();
},

browsersupport: function() {
if (typeof FileReader=='undefined') {
return false;
}
if (typeof FormData=='undefined') {
return false;
}
return true;
},

initevents: function() {
Y.on('dragenter', this.dragenter, this.container, this);
Y.on('dragleave', this.dragleave, this.container, this);
Y.on('dragover', this.dragover, this.container, this);
Y.on('drop', this.drop, this.container, this);
},

initinstructions: function() {
Y.one('#dndenabled-'+this.client_id).setStyle('display', 'inline');
},

dragenter: function(e) {
if (!this.hasfiles(e) || this.reachedmaxfiles()) {
return true;
}

e.preventDefault();
e.stopPropagation();

this.entercount++;
if (this.entercount >= 2) {
this.entercount = 2; // Just moved over a child element - nothing to do
return false;
}

this.showuploadready();
return false;
},

dragleave: function(e) {
if (!this.hasfiles(e) || this.reachedmaxfiles()) {
return true;
}

e.preventDefault();
e.stopPropagation();

this.entercount--;
if (this.entercount == 1) {
return false; // Just moved over a child element - nothing to do
}

this.entercount = 0;
this.hideuploadready();
return false;
},

dragover: function(e) {
if (!this.hasfiles(e) || this.reachedmaxfiles()) {
return true;
}

e.preventDefault();
e.stopPropagation();

return false;
},

drop: function(e) {
if (!this.hasfiles(e) || this.reachedmaxfiles()) {
return true;
}

e.preventDefault();
e.stopPropagation();
this.entercount = 0;

this.hideuploadready();

var files = e._event.dataTransfer.files;
var currentfilecount = this.filemanager.filecount;

for (var i=0, f; f=files[i]; i++) {
if (currentfilecount >= this.maxfiles && this.maxfiles != -1) {
break;
}
if (this.uploadfile(f)) {
currentfilecount++;
}
}

return false;
},

hasfiles: function(e) {
var types = e._event.dataTransfer.types;
for (var i=0; i<types.length; i++) {
if (types[i] == 'Files') {
return true;
}
}
return false;
},

reachedmaxfiles: function() {
if (this.filemanager.filecount >= this.maxfiles && this.maxfiles != -1) {
return true;
}
return false;
},

showuploadready: function() {
this.container.addClass('dndupload-over');
},

hideuploadready: function() {
this.container.removeClass('dndupload-over');
},

redrawfilemanager: function() {
this.filemanager.refresh(this.filemanager.currentpath);
},

uploadfile: function(file) {
if (file.size > this.maxbytes && this.maxbytes > 0) {
alert(M.util.get_string('uploadformlimit', 'moodle')+"\n'"+file.name+"'");
return false;
}

this.uploadingcount++;

// 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
var xhr = new XMLHttpRequest();
var self = this;
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
var result = JSON.parse(xhr.responseText);
if (result) {
self.uploadingcount--;
if (self.uploadingcount <= 0) {
self.uploadingcount = 0;
self.redrawfilemanager();
}
}
}
}
};

var url = this.api + '?action=upload';

var formdata = new FormData();
formdata.append('repo_upload_file', file);
formdata.append('sesskey', M.cfg.sesskey);
formdata.append('repo_id', this.upload_repo);
formdata.append('itemid', this.itemid);
formdata.append('savepath', this.filemanager.currentpath);

xhr.open("POST", url, true);
xhr.send(formdata);
}
});

new DndUploadHelper(options);
};
15 changes: 13 additions & 2 deletions lib/form/filemanager.php
Expand Up @@ -239,7 +239,7 @@ public function __construct(stdClass $options) {
* @return string HTML fragment
*/
function form_filemanager_render($options) {
global $CFG, $OUTPUT, $PAGE;
global $CFG, $OUTPUT, $PAGE, $DB;

$fm = new form_filemanaer_x($options); //TODO: this is unnecessary here, the nested options are getting too complex

Expand Down Expand Up @@ -273,7 +273,16 @@ function form_filemanager_render($options) {
$extra = '';
}

$options->enabledndupload = isset($CFG->enabledndupload) && $CFG->enabledndupload;
if ($options->enabledndupload) {
$options->upload_repo = $DB->get_field('repository', 'id', array('type'=>'upload'));
if (!$options->upload_repo) {
$options->enabledndupload = false;
}
}

$maxsize = get_string('maxfilesize', 'moodle', display_size(get_max_upload_file_size($CFG->maxbytes, $course_maxbytes, $options->maxbytes)));
$strdndenabled = get_string('dndenabled', 'moodle').$OUTPUT->help_icon('dndenabled');
$html .= <<<FMHTML
<div class="filemanager-loading mdl-align" id='filemanager-loading-{$client_id}'>
$icon_progress
Expand All @@ -285,6 +294,7 @@ function form_filemanager_render($options) {
<input type="button" class="fm-btn-mkdir" id="btncrt-{$client_id}" onclick="return false" value="{$strmakedir}" />
<input type="button" class="fm-btn-download" id="btndwn-{$client_id}" onclick="return false" {$extra} value="{$strdownload}" />
<span> $maxsize </span>
<span id="dndenabled-{$client_id}" style="display: none"> - $strdndenabled </span>
</div>
<div class="filemanager-container" id="filemanager-{$client_id}">
<ul id="draftfiles-{$client_id}" class="fm-filelist">
Expand Down Expand Up @@ -313,7 +323,8 @@ function form_filemanager_render($options) {
array('cannotdeletefile', 'error'), array('confirmdeletefile', 'repository'),
array('nopathselected', 'repository'), array('popupblockeddownload', 'repository'),
array('draftareanofiles', 'repository'), array('path', 'moodle'), array('setmainfile', 'repository'),
array('moving', 'repository'), array('files', 'moodle')
array('moving', 'repository'), array('files', 'moodle'),
array('uploadformlimit', 'moodle')
)
);
$PAGE->requires->js_module($module);
Expand Down
3 changes: 3 additions & 0 deletions theme/base/style/core.css
Expand Up @@ -518,6 +518,7 @@ body.tag .managelink {padding: 5px;}
.filemanager-toolbar {margin: 5px 0;}
.filemanager-toolbar a {border: 1px solid #AACCEE;background: #F4FAFF;color: black;padding: 3px;}
.filemanager-toolbar a:hover {background: #FFFFFF;}
.filemanager-toolbar .helplink a {border: 0px; background: transparent;}
.fm-breadcrumb {margin:0;}
.filemanager-container {padding: 5px;margin: 6px 0;background: #E9F4FF;border: #AACCEE 1px solid}
.filemanager-container ul{margin:0;padding:0;}
Expand All @@ -531,6 +532,8 @@ body.tag .managelink {padding: 5px;}
.fm-file-entry{border: 1px solid red;}
.fm-operation {font-weight: bold;}

.filemanager-container.dndupload-over {background: #8EF947;}

/*
* Backup and Restore CSS
*/
Expand Down

0 comments on commit e74580a

Please sign in to comment.