From 1ceee593609c421f1507438304775774ba166251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thierry=20Fran=C3=A7ois?= Date: Sun, 7 Sep 2014 20:18:32 +0300 Subject: [PATCH] NEW Rewrite of bulk editing save FIX #76 Bulk editing now use the normal Form submission system and handles all field type, including ajax dependent --- bulkManager/BULK_MANAGER.md | 2 - .../code/GridFieldBulkActionEditHandler.php | 255 +++++++++++++----- .../code/GridFieldBulkActionHandler.php | 2 +- bulkManager/code/GridFieldBulkManager.php | 86 +----- .../javascript/GridFieldBulkEditingForm.js | 68 +---- lang/en.yml | 3 +- 6 files changed, 195 insertions(+), 221 deletions(-) diff --git a/bulkManager/BULK_MANAGER.md b/bulkManager/BULK_MANAGER.md index 36f9892..b975f8b 100644 --- a/bulkManager/BULK_MANAGER.md +++ b/bulkManager/BULK_MANAGER.md @@ -14,8 +14,6 @@ The component's options can be configurated individually or in bulk through the ### $config overview The available configuration options are: * 'editableFields' : array of string referencing specific CMS fields available for editing -* 'fieldsNameBlacklist' : array of string referencing the names of fields that wont be available for editing -* 'readOnlyFieldClasses' : array of string referencing types (ClassName) of fields that will be transformed to read only (always includes `GridField` and `UploadField`) ## Custom actions You can remove or add individual action or replace them all via `addBulkAction()` and `removeBulkAction()` diff --git a/bulkManager/code/GridFieldBulkActionEditHandler.php b/bulkManager/code/GridFieldBulkActionEditHandler.php index 4ff688a..f8ead5d 100644 --- a/bulkManager/code/GridFieldBulkActionEditHandler.php +++ b/bulkManager/code/GridFieldBulkActionEditHandler.php @@ -12,7 +12,11 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler * RequestHandler allowed actions * @var array */ - private static $allowed_actions = array('edit', 'update'); + private static $allowed_actions = array( + 'index', + 'bulkEditForm', + 'recordEditForm' + ); /** @@ -20,18 +24,30 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler * @var array */ private static $url_handlers = array( - 'bulkEdit/update' => 'update', - 'bulkEdit' => 'edit' + 'bulkEdit/bulkEditForm' => 'bulkEditForm', + 'bulkEdit/recordEditForm' => 'recordEditForm', + 'bulkEdit' => 'index' ); + /** + * Return URL to this RequestHandler + * @param string $action Action to append to URL + * @return string URL + */ + public function Link($action = null) + { + return Controller::join_links(parent::Link(), 'bulkEdit', $action); + } + + /** * Return a form for all the selected DataObjects * with their respective editable fields. * * @return Form Selected DataObjects editable fields */ - public function editForm() + public function bulkEditForm() { $crumbs = $this->Breadcrumbs(); if($crumbs && $crumbs->count()>=2) @@ -42,13 +58,11 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler $actions = new FieldList(); $actions->push( - FormAction::create('SaveAll', _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.SAVE_BTN_LABEL', 'Save all')) - ->setAttribute('id', 'bulkEditingUpdateBtn') - ->addExtraClass('ss-ui-action-constructive cms-panel-link') + FormAction::create('doSave', _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.SAVE_BTN_LABEL', 'Save all')) + ->setAttribute('id', 'bulkEditingSaveBtn') + ->addExtraClass('ss-ui-action-constructive') ->setAttribute('data-icon', 'accept') - ->setAttribute('data-url', $this->gridField->Link('bulkaction/bulkEdit/update')) ->setUseButtonTag(true) - ->setAttribute('src', '')//changes type to image so isn't hooked by default actions handlers ); $actions->push( @@ -70,6 +84,7 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler $singleton = singleton($modelClass); $titleModelClass = (($editingCount > 1) ? $singleton->i18n_plural_name() : $singleton->i18n_singular_name()); + //some cosmetics $headerText = _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.HEADER_TEXT', 'Editing {count} {class}', array( @@ -85,10 +100,12 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler $toggle = LiteralField::create('bulkEditToggle', '' . _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.TOGGLE_ALL_LINK', 'Show/Hide all') . ''); $recordsFieldList->push($toggle); - + + + //fetch fields for each record and push to fieldList foreach ( $recordList as $id ) - { - $record = DataObject::get_by_id($modelClass, $id); + { + $record = DataObject::get_by_id($modelClass, $id); $recordEditingFields = $this->getRecordEditingFields($record); $toggleField = ToggleCompositeField::create( @@ -103,18 +120,72 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler $recordsFieldList->push($toggleField); } - $form = new Form( + $bulkEditForm = Form::create( $this, - 'BulkEditingForm', + 'recordEditForm', //recordEditForm name is here to trick SS to pass all subform request to recordEditForm() $recordsFieldList, $actions - ); + ); if($crumbs && $crumbs->count()>=2){ - $form->Backlink = $one_level_up->Link; + $bulkEditForm->Backlink = $one_level_up->Link; } - return $form; + //override form action URL back to bulkEditForm + //and add record ids GET var + $bulkEditForm->setAttribute( + 'action', + $this->Link('bulkEditForm?records[]='.implode('&', $recordList)) + ); + + return $bulkEditForm; + } + + + /** + * Return's a form with only one record's fields + * Used for bulkEditForm subForm requests via ajax + * + * @return Form Currently being edited form + */ + public function recordEditForm() + { + //clone current request : used to figure out what record we are asking + $request = clone $this->request; + $recordInfo = $request->shift(); + + //shift request till we find the requested field + while ($recordInfo) + { + if ( $unescapedRecordInfo = $this->unEscapeFieldName($recordInfo) ) + { + $id = $unescapedRecordInfo['id']; + $fieldName = $unescapedRecordInfo['name']; + + $action = $request->shift(); + break; + } + else{ + $recordInfo = $request->shift(); + } + } + + //generate a form with only that requested record's fields + if ( $id ) + { + $modelClass = $this->gridField->getModelClass(); + $record = DataObject::get_by_id($modelClass, $id); + + $cmsFields = $record->getCMSFields(); + $recordEditingFields = $this->getRecordEditingFields($record); + + return Form::create( + $this->gridField, + 'recordEditForm', + FieldList::create($recordEditingFields), + FieldList::create() + ); + } } @@ -159,8 +230,6 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler { $config = $this->component->getConfig(); $editableFields = $config['editableFields']; - $fieldsNameBlacklist = $config['fieldsNameBlacklist']; - $readOnlyClasses = $config['readOnlyFieldClasses']; // get all dataFields or just the ones allowed in config if ( $editableFields ) @@ -179,39 +248,62 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler $dataFields = $fields->dataFields(); } - // remove and/or set readonly fields in blacklists - foreach ($dataFields as $name => $field) - { - if ( in_array($name, $fieldsNameBlacklist) ) - { - unset( $dataFields[$name] ); - } - else if ( in_array(get_class($field), $readOnlyClasses) ) - { - $newField = $field->performReadonlyTransformation(); - $dataFields[$name] = $newField; - } - } - // escape field names with unique prefix foreach ( $dataFields as $name => $field ) { - $field->Name = 'record_' . $id . '_' . $name; + $field->Name = $this->escapeFieldName($id, $name); $dataFields[$name] = $field; } return $dataFields; } + + + /** + * Escape a fieldName with a unique prefix + * + * @param integer $recordID Record id from who the field belongs + * @param string $name Field name + * @return string Escaped field name + */ + protected function escapeFieldName($recordID, $name) + { + return 'record_' . $recordID . '_' . $name; + } + + + /** + * Un-escape a previously escaped field name + * + * @param string $fieldName Escaped field name + * @return array|false Fasle if the fieldName was not escaped. Or Array map with record 'id' and field 'name' + */ + protected function unEscapeFieldName($fieldName) + { + $parts = array(); + $match = preg_match('/record_(\d+)_(\w+)/i', $fieldName, $parts); + + if ( !$match ) + { + return false; + } + else{ + return array( + 'id' => $parts[1], + 'name' => $parts[2], + ); + } + } /** - * Creates and return the editing interface + * Creates and return the bulk editing interface * * @return string Form's HTML */ - public function edit() + public function index() { - $form = $this->editForm(); + $form = $this->bulkEditForm(); $form->setTemplate('LeftAndMain_EditForm'); $form->addExtraClass('center cms-content'); $form->setAttribute('data-pjax-fragment', 'CurrentForm Content'); @@ -236,49 +328,72 @@ class GridFieldBulkActionEditHandler extends GridFieldBulkActionHandler } } - + /** - * Saves the changes made in the bulk edit into the dataObject + * Handles bulkEditForm submission + * and parses and saves each records data * - * @return JSON + * @param array $data Sumitted form data + * @param Form $form Form */ - public function update() - { - $data = $this->request->requestVars(); - $return = array(); - $className = $this->gridField->list->dataClass; + public function doSave($data, $form) + { + $className = $this->gridField->list->dataClass; + $singleton = singleton($className); - if ( isset($data['url']) ) unset($data['url']); - if ( isset($data['cacheBuster']) ) unset($data['cacheBuster']); - if ( isset($data['locale']) ) unset($data['locale']); + $formsData = array(); + $ids = array(); + $done = 0; - foreach ($data as $recordID => $recordDataSet) + //unescape and sort form data per record ID + foreach ($data as $fieldName => $value) { - $record = DataObject::get_by_id($className, $recordID); - foreach($recordDataSet as $recordData) + if ( $fieldInfo = $this->unEscapeFieldName($fieldName) ) { - $field = preg_replace('/record_(\d+)_(\w+)/i', '$2', $recordData['name']); - $value = $recordData['value']; + if ( !isset($formsData[$fieldInfo['id']]) ) + { + $formsData[$fieldInfo['id']] = array(); + } - if ( $record->hasMethod($field) ) - { - $list = $record->$field(); - $list->setByIDList($value); - } - else{ - $record->setCastedField($field, $value); - } + $formsData[$fieldInfo['id']][$fieldInfo['name']] = $value; } - $done = $record->write(); - array_push($return, array( - 'id' => $done, - 'title' => $record->getTitle() - )); } - return json_encode(array( - 'done' => 1, - 'records' => $return - ), JSON_NUMERIC_CHECK); + //process each record's form data and save + foreach ($formsData as $recordID => $recordData) + { + $record = DataObject::get_by_id($className, $recordID); + $recordForm = Form::create( + $this, "RecordForm", + $record->getCMSFields(), + FieldList::create() + ); + + $recordForm->loadDataFrom($recordData); + $recordForm->saveInto($record); + $id = $record->write(); + + array_push($ids, $record->ID); + + if ( $id ) + { + $done++; + } + } + + //compose form message + $messageModelClass = (($editingCount > 1) ? $singleton->i18n_plural_name() : $singleton->i18n_singular_name()); + $message = _t('GRIDFIELD_BULKMANAGER_EDIT_HANDLER.SAVE_RESULT_TEXT', + '{count} {class} saved successfully.', + array( + 'count' => $done, + 'class' => $messageModelClass + ) + ); + $form->sessionMessage($message, 'good'); + + //return back to form + return Controller::curr()->redirect($this->Link('?records[]='.implode('&records[]=', $ids))); + //return Controller::curr()->redirect($form->Backlink); //returns to gridField } } diff --git a/bulkManager/code/GridFieldBulkActionHandler.php b/bulkManager/code/GridFieldBulkActionHandler.php index 2ca13bb..91ca627 100644 --- a/bulkManager/code/GridFieldBulkActionHandler.php +++ b/bulkManager/code/GridFieldBulkActionHandler.php @@ -56,7 +56,7 @@ class GridFieldBulkActionHandler extends RequestHandler */ public function Link($action = null) { - return Controller::join_links($this->gridField->Link(), 'bulkediting', $action); + return Controller::join_links($this->gridField->Link(), 'bulkAction', $action); } diff --git a/bulkManager/code/GridFieldBulkManager.php b/bulkManager/code/GridFieldBulkManager.php index a41d2ed..e8ecd47 100644 --- a/bulkManager/code/GridFieldBulkManager.php +++ b/bulkManager/code/GridFieldBulkManager.php @@ -12,27 +12,16 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr * component configuration * * 'editableFields' => fields editable on the Model - * 'readOnlyFieldClasses' => field types that will be converted to readonly - * 'fieldsNameBlacklist' => fields that will be removed from the automatic form generation * 'actions' => maps of action name and configuration * * @var array */ protected $config = array( - 'editableFields' => null, - 'fieldsNameBlacklist' => array(), - 'readOnlyFieldClasses' => array(), - 'actions' => array() + 'editableFields' => null, + 'actions' => array() ); - /** - * Holds any class that should not be used as they break the component - * These cannot be removed from the blacklist - */ - protected $readOnlyFieldClasses = array('GridField', 'UploadField'); - - /** * GridFieldBulkManager component constructor * @@ -42,7 +31,6 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr public function __construct($editableFields = null, $defaultActions = true) { if ( $editableFields != null ) $this->setConfig ( 'editableFields', $editableFields ); - $this->config['readOnlyFieldClasses'] = $this->readOnlyFieldClasses; if ( $defaultActions ) { @@ -102,17 +90,11 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr user_error("Bulk actions must be edited via addBulkAction() and removeBulkAction()", E_USER_ERROR); } - if ( ($reference == 'readOnlyFieldClasses' || $reference == 'fieldsNameBlacklist' || $reference == 'editableFields') && !is_array($value) ) + if ( ($reference == 'editableFields') && !is_array($value) ) { $value = array($value); } - //makes sure $readOnlyFieldClasses are in no matter what - if ( $reference == 'readOnlyFieldClasses' ) - { - $value = array_unique( array_merge($value, $this->readOnlyFieldClasses) ); - } - $this->config[$reference] = $value; return $this; @@ -130,62 +112,6 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr if ( $reference ) return $this->config[$reference]; else return $this->config; } - - - /** - * Add a field to the editable fields blacklist - * - * @param string $fieldName - * @return boolean - */ - function addFieldNameToBlacklist ( $fieldName ) - { - return array_push( $this->config['fieldsNameBlacklist'], $fieldName); - } - - - /** - * Add a class to the readonly list - * - * @param string $className - * @return boolean - */ - function addClassToReadOnlyList ( $className ) - { - return array_push( $this->config['readOnlyFieldClasses'], $className); - } - - - /** - * Remove a field to the editable fields blacklist - * - * @param string $fieldName - * @return boolean - */ - function removeFieldNameFromBlacklist ( $fieldName ) - { - if (key_exists($fieldName, $this->config['fieldsNameBlacklist'])) { - return delete( $this->config['fieldsNameBlacklist'][$fieldName] ); - }else{ - return false; - } - } - - - /** - * Remove a class to the readonly list - * - * @param string $className - * @return boolean - */ - function removeClassFromReadOnlyList ( $className ) - { - if (key_exists($className, $this->config['readOnlyFieldClasses']) && !in_array($className, $this->forbiddenFieldsClasses)) { - return delete( $this->config['readOnlyFieldClasses'][$className] ); - }else{ - return false; - } - } /** @@ -379,7 +305,7 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr 'Menu' => $dropDownActionsList->FieldHolder(), 'Button' => array( 'Label' => _t('GRIDFIELD_BULK_MANAGER.ACTION_BTN_LABEL', 'Go'), - 'DataURL' => $gridField->Link('bulkaction'), + 'DataURL' => $gridField->Link('bulkAction'), 'Icon' => $this->config['actions'][$firstAction]['config']['icon'], 'DataConfig' => htmlspecialchars(json_encode($actionsConfig), ENT_QUOTES, 'UTF-8') ), @@ -410,7 +336,7 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr */ public function getURLHandlers($gridField) { return array( - 'bulkaction' => 'handlebulkaction' + 'bulkAction' => 'handleBulkAction' ); } @@ -427,7 +353,7 @@ class GridFieldBulkManager implements GridField_HTMLProvider, GridField_ColumnPr * @param SS_HTTPRequest $request * @return mixed */ - public function handlebulkaction($gridField, $request) + public function handleBulkAction($gridField, $request) { $controller = $gridField->getForm()->Controller(); diff --git a/bulkManager/javascript/GridFieldBulkEditingForm.js b/bulkManager/javascript/GridFieldBulkEditingForm.js index 1827746..6f6e633 100644 --- a/bulkManager/javascript/GridFieldBulkEditingForm.js +++ b/bulkManager/javascript/GridFieldBulkEditingForm.js @@ -10,7 +10,7 @@ onunmatch: function(){}, onclick: function(e) { - var toggleFields = this.parents('#Form_BulkEditingForm').find('.ss-toggle .ui-accordion-header'), + var toggleFields = this.parents('form').find('.ss-toggle .ui-accordion-header'), state = this.data('state') ; @@ -58,71 +58,5 @@ } }); - - /** - * Save all button - * process all field holders with updates - */ - $('#bulkEditingUpdateBtn').entwine({ - onmatch: function(){}, - onunmatch: function(){}, - onclick: function(e){ - e.stopImmediatePropagation(); - - var $fieldHolders = $('div.bulkEditingFieldHolder.hasUpdate'), - url = this.data('url'), - data = {}, - cacheBuster = new Date().getTime() + '_' + this.attr('name') - ; - - if ( $fieldHolders.length > 0 ) - { - this.addClass('loading'); - } - else{ - return; - } - - if ( url.indexOf('?') !== -1 ) - { - cacheBuster = '&cacheBuster=' + cacheBuster; - } - else{ - cacheBuster = '?cacheBuster=' + cacheBuster; - } - - $fieldHolders.each(function(){ - var $this = $(this); - data[$this.data('id')] = $this.find(':input').serializeArray(); - }); - - $.ajax({ - url: url + cacheBuster, - data: data, - type: "POST", - context: this - }).success(function(data, textStatus, jqXHR){ - try{ - data = $.parseJSON(data); - }catch(er){} - - $.each(data.records, function(index, record){ - var $fieldHolder = $('#Form_BulkEditingForm_RecordFields_'+record.id), - $header = $fieldHolder.find('.ui-accordion-header') - ; - - $fieldHolder.removeClass('hasUpdate').addClass('updated'); - $header.find('a').html(record.title); - if ( $header.hasClass('ui-state-active') ) - { - $header.click(); - } - }); - - this.removeClass('loading'); - }); - } - }); - }); }(jQuery)); \ No newline at end of file diff --git a/lang/en.yml b/lang/en.yml index d1da4ba..c234d57 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -19,4 +19,5 @@ en: HEADER_TEXT: Editing {count} {class} TOGGLE_ALL_LINK: Show/Hide all SAVE_BTN_LABEL: Save all - CANCEL_BTN_LABEL: Cancel \ No newline at end of file + CANCEL_BTN_LABEL: Cancel + SAVE_RESULT_TEXT: {count} {class} saved successfully. \ No newline at end of file