mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
ENHANCEMENT Attaching files from /assets through UploadField
This commit is contained in:
parent
55ddbd38bc
commit
c00f0406e9
@ -30,7 +30,9 @@ class UploadField extends FileField {
|
||||
*/
|
||||
public static $allowed_actions = array(
|
||||
'upload',
|
||||
'handleItem'
|
||||
'attach',
|
||||
'handleItem',
|
||||
'handleSelect',
|
||||
);
|
||||
|
||||
/**
|
||||
@ -38,6 +40,7 @@ class UploadField extends FileField {
|
||||
*/
|
||||
public static $url_handlers = array(
|
||||
'item/$ID' => 'handleItem',
|
||||
'select' => 'handleSelect',
|
||||
'$Action!' => '$Action',
|
||||
);
|
||||
|
||||
@ -293,6 +296,13 @@ class UploadField extends FileField {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getAttributes() {
|
||||
return array_merge(
|
||||
parent::getAttributes(),
|
||||
array('data-selectdialog-url', $this->Link('select'))
|
||||
);
|
||||
}
|
||||
|
||||
public function Field() {
|
||||
$record = $this->getRecord();
|
||||
$name = $this->getName();
|
||||
@ -329,6 +339,8 @@ class UploadField extends FileField {
|
||||
|
||||
$config = array(
|
||||
'url' => $this->Link('upload'),
|
||||
'urlSelectDialog' => $this->Link('select'),
|
||||
'urlAttach' => $this->Link('attach'),
|
||||
'acceptFileTypes' => '.+$',
|
||||
'maxNumberOfFiles' => $this->getConfig('allowedMaxFileNumber')
|
||||
);
|
||||
@ -392,6 +404,14 @@ class UploadField extends FileField {
|
||||
return Object::create('UploadField_ItemHandler', $this, $itemID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SS_HTTPRequest $request
|
||||
* @return UploadField_ItemHandler
|
||||
*/
|
||||
public function handleSelect(SS_HTTPRequest $request) {
|
||||
return Object::create('UploadField_SelectHandler', $this, $this->folderName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to handle upload of a single file
|
||||
*
|
||||
@ -467,6 +487,34 @@ class UploadField extends FileField {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add existing {@link File} records to the relationship.
|
||||
*/
|
||||
public function attach($request) {
|
||||
if(!$request->isPOST()) return $this->httpError(403);
|
||||
if(!$this->managesRelation()) return $this->httpError(403);
|
||||
|
||||
$return = array();
|
||||
|
||||
$files = DataList::create('File')->byIDs($request->postVar('ids'));
|
||||
foreach($files as $file) {
|
||||
$this->attachFile($file);
|
||||
$file = $this->customiseFile($file);
|
||||
$return[] = array(
|
||||
'id' => $file->ID,
|
||||
'name' => $file->getTitle() . '.' . $file->getExtension(),
|
||||
'url' => $file->getURL(),
|
||||
'thumbnail_url' => $file->UploadFieldThumbnailURL,
|
||||
'edit_url' => $file->UploadFieldEditLink,
|
||||
'size' => $file->getAbsoluteSize(),
|
||||
'buttons' => $file->UploadFieldFileButtons
|
||||
);
|
||||
}
|
||||
$response = new SS_HTTPResponse(Convert::raw2json($return));
|
||||
$response->addHeader('Content-Type', 'application/json');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param File
|
||||
*/
|
||||
@ -742,3 +790,84 @@ class UploadField_ItemHandler extends RequestHandler {
|
||||
|
||||
}
|
||||
|
||||
|
||||
class UploadField_SelectHandler extends RequestHandler {
|
||||
|
||||
/**
|
||||
* @var UploadField
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var String
|
||||
*/
|
||||
protected $folderName;
|
||||
|
||||
public static $url_handlers = array(
|
||||
'$Action!' => '$Action',
|
||||
'' => 'index',
|
||||
);
|
||||
|
||||
function __construct($parent, $folderName = null) {
|
||||
$this->parent = $parent;
|
||||
$this->folderName = $folderName;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
function index() {
|
||||
return $this->renderWith('CMSDialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @return string
|
||||
*/
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links($this->parent->Link(), '/select/', $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Form
|
||||
*/
|
||||
function Form() {
|
||||
$action = new FormAction('doAttach', _t('UploadField.AttachFile', 'Attach file(s)'));
|
||||
$action->addExtraClass('ss-ui-action-constructive');
|
||||
return new Form(
|
||||
$this,
|
||||
'Form',
|
||||
new FieldList($this->getListField()),
|
||||
new FieldList($action)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormField
|
||||
*/
|
||||
protected function getListField() {
|
||||
$folder = $this->getFolder();
|
||||
$config = GridFieldConfig::create();
|
||||
$config->addComponent(new GridFieldSortableHeader());
|
||||
$config->addComponent(new GridFieldFilter());
|
||||
$config->addComponent(new GridFieldDefaultColumns());
|
||||
$config->addComponent(new GridFieldPaginator(10));
|
||||
|
||||
$field = new GridField('Files', false, $folder->stageChildren(), $config);
|
||||
$field->setAttribute('data-selectable', true);
|
||||
if($this->parent->getConfig('allowedMaxFileNumber') > 1) $field->setAttribute('data-multiselect', true);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Folder
|
||||
*/
|
||||
function getFolder() {
|
||||
return Folder::find_or_make($this->folderName);
|
||||
}
|
||||
|
||||
function doAttach($data, $form) {
|
||||
// TODO Only implemented via JS for now
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
(function($) {
|
||||
$.widget('blueimpUIX.fileupload', $.blueimpUI.fileupload, {
|
||||
_initTemplates: function() {
|
||||
this.options.templateContainer = document.createElement(
|
||||
this._files.prop('nodeName')
|
||||
);
|
||||
this.options.uploadTemplate = window.tmpl(this.options.uploadTemplateName);
|
||||
this.options.downloadTemplate = window.tmpl(this.options.downloadTemplateName);
|
||||
},
|
||||
this.options.templateContainer = document.createElement(
|
||||
this._files.prop('nodeName')
|
||||
);
|
||||
this.options.uploadTemplate = window.tmpl(this.options.uploadTemplateName);
|
||||
this.options.downloadTemplate = window.tmpl(this.options.downloadTemplateName);
|
||||
},
|
||||
_enableFileInputButton: function() {
|
||||
$.blueimpUI.fileupload.prototype._enableFileInputButton.call(this);
|
||||
this.element.find('.ss-uploadfield-addfile').show();
|
||||
@ -26,10 +26,15 @@
|
||||
});
|
||||
$.entwine('ss', function($) {
|
||||
$('div.ss-upload').entwine({
|
||||
|
||||
Config: null,
|
||||
|
||||
onmatch: function() {
|
||||
var fileInput = this.find('input');
|
||||
var dropZone = this.find('.ss-uploadfield-dropzone');
|
||||
var config = $.parseJSON(fileInput.data('config').replace(/'/g,'"'));
|
||||
|
||||
this.setConfig(config);
|
||||
this.fileupload($.extend(true,
|
||||
{
|
||||
formData: function(form) {
|
||||
@ -52,22 +57,22 @@
|
||||
emptyResult: ss.i18n._t('UploadField.EMPTYRESULT')
|
||||
},
|
||||
send: function(e, data) {
|
||||
if (data.context && data.dataType && data.dataType.substr(0, 6) === 'iframe') {
|
||||
// Iframe Transport does not support progress events.
|
||||
// In lack of an indeterminate progress bar, we set
|
||||
// the progress to 100%, showing the full animated bar:
|
||||
data.total = 1;
|
||||
data.loaded = 1;
|
||||
$(this).data('fileupload').options.progress(e, data);
|
||||
}
|
||||
if (data.context && data.dataType && data.dataType.substr(0, 6) === 'iframe') {
|
||||
// Iframe Transport does not support progress events.
|
||||
// In lack of an indeterminate progress bar, we set
|
||||
// the progress to 100%, showing the full animated bar:
|
||||
data.total = 1;
|
||||
data.loaded = 1;
|
||||
$(this).data('fileupload').options.progress(e, data);
|
||||
}
|
||||
},
|
||||
progress: function(e, data) {
|
||||
if (data.context) {
|
||||
var value = parseInt(data.loaded / data.total * 100, 10) + '%';
|
||||
data.context.find('.ss-uploadfield-item-status').html((data.total == 1)?ss.i18n._t('UploadField.LOADING'):value);
|
||||
data.context.find('.ss-uploadfield-item-progressbarvalue').css('width', value);
|
||||
}
|
||||
}
|
||||
if (data.context) {
|
||||
var value = parseInt(data.loaded / data.total * 100, 10) + '%';
|
||||
data.context.find('.ss-uploadfield-item-status').html((data.total == 1)?ss.i18n._t('UploadField.LOADING'):value);
|
||||
data.context.find('.ss-uploadfield-item-progressbarvalue').css('width', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
config,
|
||||
{
|
||||
@ -82,6 +87,62 @@
|
||||
dropZone.show(); // drag&drop avaliable
|
||||
}
|
||||
this._super();
|
||||
},
|
||||
|
||||
openSelectDialog: function() {
|
||||
// Create dialog and load iframe
|
||||
var self = this, config = this.getConfig(), dialogId = 'ss-uploadfield-dialog-' + this.attr('id'), dialog = jQuery('#' + dialogId);
|
||||
if(!dialog.length) dialog = jQuery('<div class="ss-uploadfield-dialog" id="' + dialogId + '" />');
|
||||
|
||||
// Show dialog
|
||||
dialog.ssdialog({iframeUrl: config['urlSelectDialog']});
|
||||
|
||||
// TODO Allow single-select
|
||||
dialog.find('iframe').bind('load', function(e) {
|
||||
var contents = $(this).contents(), gridField = contents.find('fieldset.ss-gridfield');
|
||||
// TODO Fix jQuery custom event bubbling across iframes on same domain
|
||||
// gridField.find('.ss-gridfield-items')).bind('selectablestop', function() {
|
||||
// });
|
||||
|
||||
// Remove top margin (easier than including new selectors)
|
||||
contents.find('table.ss-gridfield').css('margin-top', 0);
|
||||
|
||||
// Can't use live() in iframes...
|
||||
contents.find('input[name=action_doAttach]').unbind('click.openSelectDialog').bind('click.openSelectDialog', function() {
|
||||
// TODO Fix entwine method calls across iframe/document boundaries
|
||||
var ids = $.map(gridField.find('.ss-gridfield-item.ui-selected'), function(el) {return $(el).data('id');});
|
||||
if(ids && ids.length) self.attachFiles(ids);
|
||||
|
||||
dialog.ssdialog('close');
|
||||
return false;
|
||||
});
|
||||
});
|
||||
dialog.ssdialog('open');
|
||||
},
|
||||
attachFiles: function(ids) {
|
||||
var self = this, config = this.getConfig();
|
||||
$.post(
|
||||
config['urlAttach'],
|
||||
{'ids': ids},
|
||||
function(data, status, xhr) {
|
||||
var fn = self.fileupload('option', 'downloadTemplate');
|
||||
self.find('.ss-uploadfield-files').append(fn({
|
||||
files: data,
|
||||
formatFileSize: function (bytes) {
|
||||
if (typeof bytes !== 'number') return '';
|
||||
if (bytes >= 1000000000) return (bytes / 1000000000).toFixed(2) + ' GB';
|
||||
if (bytes >= 1000000) return (bytes / 1000000).toFixed(2) + ' MB';
|
||||
return (bytes / 1000).toFixed(2) + ' KB';
|
||||
},
|
||||
options: self.fileupload('option')
|
||||
}));
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
$('div.ss-upload *').entwine({
|
||||
getUploadField: function() {
|
||||
return this.parents('div.ss-upload:first');
|
||||
}
|
||||
});
|
||||
$('div.ss-upload .ss-uploadfield-files .ss-uploadfield-item').entwine({
|
||||
@ -160,5 +221,11 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
$('div.ss-upload .ss-uploadfield-fromfiles').entwine({
|
||||
onclick: function(e) {
|
||||
e.preventDefault();
|
||||
this.getUploadField().openSelectDialog();
|
||||
}
|
||||
});
|
||||
});
|
||||
}(jQuery));
|
@ -375,6 +375,84 @@
|
||||
|
||||
}
|
||||
|
||||
function testSelect() {
|
||||
$this->loginWithPermission('ADMIN');
|
||||
|
||||
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
|
||||
$file4 = $this->objFromFixture('File', 'file4');
|
||||
$file5 = $this->objFromFixture('File', 'file5');
|
||||
$fileSubfolder = $this->objFromFixture('File', 'file-subfolder');
|
||||
$fileNoEdit = $this->objFromFixture('File', 'file-noedit');
|
||||
|
||||
$response = $this->get('UploadFieldTest_Controller/Form/field/ManyManyFiles/select/');
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
// A bit too much coupling with GridField, but a full template overload would make things too complex
|
||||
$parser = new CSSContentParser($response->getBody());
|
||||
$items = $parser->getBySelector('.ss-gridfield-item');
|
||||
$itemIDs = array_map(create_function('$el', 'return (int)$el["data-id"];'), $items);
|
||||
$this->assertContains($file4->ID, $itemIDs, 'Contains file in assigned folder');
|
||||
$this->assertNotContains($fileSubfolder->ID, $itemIDs, 'Does not contain file in subfolder');
|
||||
}
|
||||
|
||||
function testAttachHasOne() {
|
||||
$this->loginWithPermission('ADMIN');
|
||||
|
||||
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
|
||||
$file1 = $this->objFromFixture('File', 'file1');
|
||||
$file2 = $this->objFromFixture('File', 'file2');
|
||||
$file3AlreadyAttached = $this->objFromFixture('File', 'file3');
|
||||
|
||||
$response = $this->post(
|
||||
'UploadFieldTest_Controller/Form/field/HasOneFile/attach',
|
||||
array('ids' => array($file1->ID/* first file should be ignored */, $file2->ID))
|
||||
);
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
||||
$this->assertEquals($file2->ID, $record->HasOneFileID, 'Attaches new relations');
|
||||
}
|
||||
|
||||
function testAttachHasMany() {
|
||||
$this->loginWithPermission('ADMIN');
|
||||
|
||||
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
|
||||
$file1 = $this->objFromFixture('File', 'file1');
|
||||
$file2 = $this->objFromFixture('File', 'file2');
|
||||
$file3AlreadyAttached = $this->objFromFixture('File', 'file3');
|
||||
|
||||
$response = $this->post(
|
||||
'UploadFieldTest_Controller/Form/field/HasManyFiles/attach',
|
||||
array('ids' => array($file1->ID, $file2->ID))
|
||||
);
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
||||
$this->assertContains($file1->ID, $record->HasManyFiles()->column('ID'), 'Attaches new relations');
|
||||
$this->assertContains($file2->ID, $record->HasManyFiles()->column('ID'), 'Attaches new relations');
|
||||
$this->assertContains($file3AlreadyAttached->ID, $record->HasManyFiles()->column('ID'), 'Does not detach existing relations');
|
||||
}
|
||||
|
||||
function testAttachManyMany() {
|
||||
$this->loginWithPermission('ADMIN');
|
||||
|
||||
$record = $this->objFromFixture('UploadFieldTest_Record', 'record1');
|
||||
$file1 = $this->objFromFixture('File', 'file1');
|
||||
$file2 = $this->objFromFixture('File', 'file2');
|
||||
$file5AlreadyAttached = $this->objFromFixture('File', 'file5');
|
||||
|
||||
$response = $this->post(
|
||||
'UploadFieldTest_Controller/Form/field/ManyManyFiles/attach',
|
||||
array('ids' => array($file1->ID, $file2->ID))
|
||||
);
|
||||
$this->assertFalse($response->isError());
|
||||
|
||||
$record = DataObject::get_by_id($record->class, $record->ID, false);
|
||||
$this->assertContains($file1->ID, $record->ManyManyFiles()->column('ID'), 'Attaches new relations');
|
||||
$this->assertContains($file2->ID, $record->ManyManyFiles()->column('ID'), 'Attaches new relations');
|
||||
$this->assertContains($file5AlreadyAttached->ID, $record->ManyManyFiles()->column('ID'), 'Does not detach existing relations');
|
||||
}
|
||||
|
||||
protected function getMockForm() {
|
||||
return new Form(new Controller(), 'Form', new FieldList(), new FieldList());
|
||||
}
|
||||
@ -518,6 +596,10 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
|
||||
$fieldDisabled->setRecord($record);
|
||||
$fieldDisabled = $fieldDisabled->performDisabledTransformation();
|
||||
|
||||
$fieldSubfolder = new UploadField('SubfolderField');
|
||||
$fieldSubfolder->setFolderName('UploadFieldTest/subfolder1');
|
||||
$fieldSubfolder->setRecord($record);
|
||||
|
||||
$form = new Form(
|
||||
$this,
|
||||
'Form',
|
||||
@ -527,7 +609,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
|
||||
$fieldHasMany,
|
||||
$fieldManyMany,
|
||||
$fieldReadonly,
|
||||
$fieldDisabled
|
||||
$fieldDisabled,
|
||||
$fieldSubfolder
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('submit')
|
||||
@ -538,7 +621,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
|
||||
'HasManyFiles',
|
||||
'ManyManyFiles',
|
||||
'ReadonlyField',
|
||||
'DisabledField'
|
||||
'DisabledField',
|
||||
'SubfolderField'
|
||||
)
|
||||
);
|
||||
return $form;
|
||||
|
@ -1,6 +1,9 @@
|
||||
Folder:
|
||||
folder1:
|
||||
Name: UploadFieldTest
|
||||
folder1-subfolder1:
|
||||
Name: subfolder1
|
||||
ParentID: =>Folder.folder1
|
||||
File:
|
||||
file1:
|
||||
Title: File1
|
||||
@ -37,6 +40,11 @@ File:
|
||||
Name: nodelete.txt
|
||||
Filename: assets/UploadFieldTest/nodelete.txt
|
||||
ParentID: =>Folder.folder1
|
||||
file-subfolder:
|
||||
Title: file-subfolder.txt
|
||||
Name: file-subfolder.txt
|
||||
Filename: assets/UploadFieldTest/subfolder1/file-subfolder.txt
|
||||
ParentID: =>Folder.folder1-subfolder1
|
||||
UploadFieldTest_Record:
|
||||
record1:
|
||||
Title: Record 1
|
||||
|
Loading…
Reference in New Issue
Block a user