ENHANCEMENT Attaching files from /assets through UploadField

This commit is contained in:
Ingo Schommer 2012-02-08 00:58:58 +01:00
parent 55ddbd38bc
commit c00f0406e9
4 changed files with 311 additions and 23 deletions

View File

@ -30,7 +30,9 @@ class UploadField extends FileField {
*/ */
public static $allowed_actions = array( public static $allowed_actions = array(
'upload', 'upload',
'handleItem' 'attach',
'handleItem',
'handleSelect',
); );
/** /**
@ -38,6 +40,7 @@ class UploadField extends FileField {
*/ */
public static $url_handlers = array( public static $url_handlers = array(
'item/$ID' => 'handleItem', 'item/$ID' => 'handleItem',
'select' => 'handleSelect',
'$Action!' => '$Action', '$Action!' => '$Action',
); );
@ -293,6 +296,13 @@ class UploadField extends FileField {
return false; return false;
} }
public function getAttributes() {
return array_merge(
parent::getAttributes(),
array('data-selectdialog-url', $this->Link('select'))
);
}
public function Field() { public function Field() {
$record = $this->getRecord(); $record = $this->getRecord();
$name = $this->getName(); $name = $this->getName();
@ -329,6 +339,8 @@ class UploadField extends FileField {
$config = array( $config = array(
'url' => $this->Link('upload'), 'url' => $this->Link('upload'),
'urlSelectDialog' => $this->Link('select'),
'urlAttach' => $this->Link('attach'),
'acceptFileTypes' => '.+$', 'acceptFileTypes' => '.+$',
'maxNumberOfFiles' => $this->getConfig('allowedMaxFileNumber') 'maxNumberOfFiles' => $this->getConfig('allowedMaxFileNumber')
); );
@ -392,6 +404,14 @@ class UploadField extends FileField {
return Object::create('UploadField_ItemHandler', $this, $itemID); 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 * Action to handle upload of a single file
* *
@ -467,6 +487,34 @@ class UploadField extends FileField {
return $response; 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 * @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
}
}

View File

@ -26,10 +26,15 @@
}); });
$.entwine('ss', function($) { $.entwine('ss', function($) {
$('div.ss-upload').entwine({ $('div.ss-upload').entwine({
Config: null,
onmatch: function() { onmatch: function() {
var fileInput = this.find('input'); var fileInput = this.find('input');
var dropZone = this.find('.ss-uploadfield-dropzone'); var dropZone = this.find('.ss-uploadfield-dropzone');
var config = $.parseJSON(fileInput.data('config').replace(/'/g,'"')); var config = $.parseJSON(fileInput.data('config').replace(/'/g,'"'));
this.setConfig(config);
this.fileupload($.extend(true, this.fileupload($.extend(true,
{ {
formData: function(form) { formData: function(form) {
@ -82,6 +87,62 @@
dropZone.show(); // drag&drop avaliable dropZone.show(); // drag&drop avaliable
} }
this._super(); 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({ $('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)); }(jQuery));

View File

@ -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() { protected function getMockForm() {
return new Form(new Controller(), 'Form', new FieldList(), new FieldList()); 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->setRecord($record);
$fieldDisabled = $fieldDisabled->performDisabledTransformation(); $fieldDisabled = $fieldDisabled->performDisabledTransformation();
$fieldSubfolder = new UploadField('SubfolderField');
$fieldSubfolder->setFolderName('UploadFieldTest/subfolder1');
$fieldSubfolder->setRecord($record);
$form = new Form( $form = new Form(
$this, $this,
'Form', 'Form',
@ -527,7 +609,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
$fieldHasMany, $fieldHasMany,
$fieldManyMany, $fieldManyMany,
$fieldReadonly, $fieldReadonly,
$fieldDisabled $fieldDisabled,
$fieldSubfolder
), ),
new FieldList( new FieldList(
new FormAction('submit') new FormAction('submit')
@ -538,7 +621,8 @@ class UploadFieldTest_Controller extends Controller implements TestOnly {
'HasManyFiles', 'HasManyFiles',
'ManyManyFiles', 'ManyManyFiles',
'ReadonlyField', 'ReadonlyField',
'DisabledField' 'DisabledField',
'SubfolderField'
) )
); );
return $form; return $form;

View File

@ -1,6 +1,9 @@
Folder: Folder:
folder1: folder1:
Name: UploadFieldTest Name: UploadFieldTest
folder1-subfolder1:
Name: subfolder1
ParentID: =>Folder.folder1
File: File:
file1: file1:
Title: File1 Title: File1
@ -37,6 +40,11 @@ File:
Name: nodelete.txt Name: nodelete.txt
Filename: assets/UploadFieldTest/nodelete.txt Filename: assets/UploadFieldTest/nodelete.txt
ParentID: =>Folder.folder1 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: UploadFieldTest_Record:
record1: record1:
Title: Record 1 Title: Record 1