Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENHANCEMENT Attaching files from /assets through UploadField

  • Loading branch information...
commit c00f0406e9becb3b4004ab3dc4616712812b1d7a 1 parent 55ddbd3
Ingo Schommer chillu authored
131 forms/UploadField.php
View
@@ -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 @@ protected function getThumbnailURLForFile(File $file) {
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 @@ public function Field() {
$config = array(
'url' => $this->Link('upload'),
+ 'urlSelectDialog' => $this->Link('select'),
+ 'urlAttach' => $this->Link('attach'),
'acceptFileTypes' => '.+$',
'maxNumberOfFiles' => $this->getConfig('allowedMaxFileNumber')
);
@@ -393,6 +405,14 @@ public function getItemHandler($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
*
* @param SS_HTTPRequest $request
@@ -468,6 +488,34 @@ public function upload(SS_HTTPRequest $request) {
}
/**
+ * 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
*/
protected function attachFile($file) {
@@ -742,3 +790,84 @@ public function doEdit(array $data, Form $form, SS_HTTPRequest $request) {
}
+
+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
+ }
+
+}
107 javascript/UploadField.js
View
@@ -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));
88 tests/forms/uploadfield/UploadFieldTest.php
View
@@ -375,6 +375,84 @@ function testDisabled() {
}
+ 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 @@ function Form() {
$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 @@ function Form() {
$fieldHasMany,
$fieldManyMany,
$fieldReadonly,
- $fieldDisabled
+ $fieldDisabled,
+ $fieldSubfolder
),
new FieldList(
new FormAction('submit')
@@ -538,7 +621,8 @@ function Form() {
'HasManyFiles',
'ManyManyFiles',
'ReadonlyField',
- 'DisabledField'
+ 'DisabledField',
+ 'SubfolderField'
)
);
return $form;
8 tests/forms/uploadfield/UploadFieldTest.yml
View
@@ -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
Please sign in to comment.
Something went wrong with that request. Please try again.