ENHANCEMENT: Refactored MemberTableList field to make better use of DataList and ManyManyList. Refactored ComplexTableField and TableListField to, stripping out as much model logic as possible.

This commit is contained in:
Sam Minnee 2011-03-30 14:28:50 +13:00
parent 3a17d5c427
commit 4a061fd071
7 changed files with 156 additions and 488 deletions

View File

@ -33,8 +33,6 @@ class MemberTableField extends ComplexTableField {
public $itemClass = 'MemberTableField_Item'; public $itemClass = 'MemberTableField_Item';
static $data_class = 'Member';
/** /**
* Set the page size for this table. * Set the page size for this table.
* @var int * @var int
@ -60,8 +58,24 @@ class MemberTableField extends ComplexTableField {
* @param boolean $hidePassword Hide the password field or not in the summary? * @param boolean $hidePassword Hide the password field or not in the summary?
*/ */
function __construct($controller, $name, $group = null, $members = null, $hidePassword = true) { function __construct($controller, $name, $group = null, $members = null, $hidePassword = true) {
$sourceClass = self::$data_class;
$SNG_member = singleton($sourceClass); if(!$members) {
if($group) {
if(is_numeric($group)) $group = DataObject::get_by_id('Group', $group);
$this->group = $group;
$members = $group->Members();
} elseif(isset($_REQUEST['ctf'][$this->Name()]["ID"]) && is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
throw new Exception("Is this still being used? It's a hack and we should remove it.");
$group = DataObject::get_by_id('Group', $_REQUEST['ctf'][$this->Name()]["ID"]);
$this->group = $group;
$members = $group->Members();
} else {
$members = DataObject::get("Member");
}
}
$SNG_member = singleton('Member');
$fieldList = $SNG_member->summaryFields(); $fieldList = $SNG_member->summaryFields();
$memberDbFields = $SNG_member->db(); $memberDbFields = $SNG_member->db();
$csvFieldList = array(); $csvFieldList = array();
@ -70,52 +84,24 @@ class MemberTableField extends ComplexTableField {
$csvFieldList[$field] = $field; $csvFieldList[$field] = $field;
} }
if($group) {
if(is_object($group)) {
$this->group = $group;
} elseif(is_numeric($group)) {
$this->group = DataObject::get_by_id('Group', $group);
}
} else if(isset($_REQUEST['ctf'][$this->Name()]["ID"]) && is_numeric($_REQUEST['ctf'][$this->Name()]["ID"])) {
$this->group = DataObject::get_by_id('Group', $_REQUEST['ctf'][$this->Name()]["ID"]);
}
if(!$hidePassword) { if(!$hidePassword) {
$fieldList["SetPassword"] = "Password"; $fieldList["SetPassword"] = "Password";
} }
$this->hidePassword = $hidePassword; $this->hidePassword = $hidePassword;
// @todo shouldn't this use $this->group? It's unclear exactly // Add a search filter
// what group it should be customising the custom Member set with.
if($members && $group) {
$this->setCustomSourceItems($this->memberListWithGroupID($members, $group));
}
parent::__construct($controller, $name, $sourceClass, $fieldList);
$SQL_search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null; $SQL_search = isset($_REQUEST['MemberSearch']) ? Convert::raw2sql($_REQUEST['MemberSearch']) : null;
if(!empty($_REQUEST['MemberSearch'])) { if(!empty($_REQUEST['MemberSearch'])) {
$searchFilters = array(); $searchFilters = array();
foreach($SNG_member->searchableFields() as $fieldName => $fieldSpec) { foreach($SNG_member->searchableFields() as $fieldName => $fieldSpec) {
if(strpos($fieldName, '.') === false) $searchFilters[] = "\"$fieldName\" LIKE '%{$SQL_search}%'"; if(strpos($fieldName, '.') === false) $searchFilters[] = "\"$fieldName\" LIKE '%{$SQL_search}%'";
} }
$this->sourceFilter[] = '(' . implode(' OR ', $searchFilters) . ')'; $members = $members->filter('(' . implode(' OR ', $searchFilters) . ')');
} }
if($this->group) { parent::__construct($controller, $name, $members, $fieldList);
user_error("MemberTableField's group setting doesn't yet work in the new-orm branch", E_USER_WARNING);
/*
$groupIDs = array($this->group->ID);
if($this->group->AllChildren()) $groupIDs = array_merge($groupIDs, $this->group->AllChildren()->column('ID'));
$this->sourceFilter[] = sprintf(
'"Group_Members"."GroupID" IN (%s)',
implode(',', $groupIDs)
);
*/
}
$this->sourceJoin = " INNER JOIN \"Group_Members\" ON \"MemberID\"=\"Member\".\"ID\"";
$this->setFieldListCsv($csvFieldList); $this->setFieldListCsv($csvFieldList);
$this->setPageSize($this->stat('page_size')); $this->setPageSize($this->stat('page_size'));
} }
@ -130,14 +116,6 @@ class MemberTableField extends ComplexTableField {
return $ret; return $ret;
} }
function sourceID() {
return ($this->group) ? $this->group->ID : 0;
}
function AddLink() {
return Controller::join_links($this->Link(), 'add');
}
function SearchForm() { function SearchForm() {
$groupID = (isset($this->group)) ? $this->group->ID : 0; $groupID = (isset($this->group)) ? $this->group->ID : 0;
$query = isset($_GET['MemberSearch']) ? $_GET['MemberSearch'] : null; $query = isset($_GET['MemberSearch']) ? $_GET['MemberSearch'] : null;
@ -168,6 +146,7 @@ class MemberTableField extends ComplexTableField {
if(!$token->checkRequest($this->controller->getRequest())) return $this->httpError(400); if(!$token->checkRequest($this->controller->getRequest())) return $this->httpError(400);
$data = $_REQUEST; $data = $_REQUEST;
$groupID = (isset($data['ctf']['ID'])) ? $data['ctf']['ID'] : null; $groupID = (isset($data['ctf']['ID'])) ? $data['ctf']['ID'] : null;
if(!is_numeric($groupID)) { if(!is_numeric($groupID)) {
@ -177,7 +156,7 @@ class MemberTableField extends ComplexTableField {
// Get existing record either by ID or unique identifier. // Get existing record either by ID or unique identifier.
$identifierField = Member::get_unique_identifier_field(); $identifierField = Member::get_unique_identifier_field();
$className = self::$data_class; $className = 'Member';
$record = null; $record = null;
if(isset($data[$identifierField])) { if(isset($data[$identifierField])) {
$record = DataObject::get_one( $record = DataObject::get_one(
@ -204,7 +183,7 @@ class MemberTableField extends ComplexTableField {
$valid = $record->validate(); $valid = $record->validate();
if($valid->valid()) { if($valid->valid()) {
$record->write(); $record->write();
$record->Groups()->add($groupID); $this->getDataList()->add($record);
$this->sourceItems(); $this->sourceItems();
@ -232,68 +211,12 @@ class MemberTableField extends ComplexTableField {
return FormResponse::respond(); return FormResponse::respond();
} }
/**
* Custom delete implementation:
* Remove member from group rather than from the database
*/
function delete() {
// Protect against CSRF on destructive action
$token = $this->getForm()->getSecurityToken();
// TODO Not sure how this is called, using $_REQUEST to be on the safe side
if(!$token->check($_REQUEST['SecurityID'])) return $this->httpError(400);
$groupID = Convert::raw2sql($_REQUEST['ctf']['ID']);
$memberID = Convert::raw2sql($_REQUEST['ctf']['childID']);
if(is_numeric($groupID) && is_numeric($memberID)) {
$member = DataObject::get_by_id('Member', $memberID);
$member->Groups()->remove($groupID);
} else {
user_error("MemberTableField::delete: Bad parameters: Group=$groupID, Member=$memberID", E_USER_ERROR);
}
return FormResponse::respond();
}
/**
* #################################
* Utility Functions
* #################################
*/
function getParentClass() {
return 'Group';
}
function getParentIdName($childClass, $parentClass) {
return 'GroupID';
}
/** /**
* ################################# * #################################
* Custom Functions * Custom Functions
* ################################# * #################################
*/ */
/**
* Customise an existing DataObjectSet of Member
* objects with a GroupID.
*
* @param DataObjectSet $members Set of Member objects to customise
* @param Group $group Group object to customise with
* @return DataObjectSet Customised set of Member objects
*/
function memberListWithGroupID($members, $group) {
$newMembers = new DataObjectSet();
foreach($members as $member) {
$newMembers->push($member->customise(array('GroupID' => $group->ID)));
}
return $newMembers;
}
function setGroup($group) {
$this->group = $group;
}
/** /**
* @return Group * @return Group
*/ */
@ -301,14 +224,6 @@ class MemberTableField extends ComplexTableField {
return $this->group; return $this->group;
} }
function setController($controller) {
$this->controller = $controller;
}
function GetControllerName() {
return $this->controller->class;
}
/** /**
* Add existing member to group by name (with JS-autocompletion) * Add existing member to group by name (with JS-autocompletion)
*/ */
@ -390,66 +305,6 @@ class MemberTableField extends ComplexTableField {
$this->controller->redirectBack(); $this->controller->redirectBack();
} }
/**
* Cached version for getting the appropraite members for this particular group.
*
* This includes getting inherited groups, such as groups under groups.
*/
function sourceItems() {
// Caching.
if($this->sourceItems) {
return $this->sourceItems;
}
// Setup limits
$limitClause = '';
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
$limitClause = ($_REQUEST['ctf'][$this->Name()]['start']) . ", {$this->pageSize}";
} else {
$limitClause = "0, {$this->pageSize}";
}
// We use the group to get the members, as they already have the bulk of the look up functions
$start = isset($_REQUEST['ctf'][$this->Name()]['start']) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
$this->sourceItems = false;
if($this->group) {
$this->sourceItems = $this->group->Members(
$this->pageSize, // limit
$start, // offset
$this->sourceFilter,
$this->sourceSort
);
} else {
$this->sourceItems = DataObject::get(self::$data_class,
$this->sourceFilter,
$this->sourceSort,
null,
array('limit' => $this->pageSize, 'start' => $start)
);
}
// Because we are not used $this->upagedSourceItems any more, and the DataObjectSet is usually the source
// that a large member set runs out of memory. we disable it here.
//$this->unpagedSourceItems = $this->group->Members('', '', $this->sourceFilter, $this->sourceSort);
$this->totalCount = ($this->sourceItems) ? $this->sourceItems->TotalItems() : 0;
return $this->sourceItems;
}
function TotalCount() {
$this->sourceItems(); // Called for its side-effect of setting total count
return $this->totalCount;
}
/**
* Handles item requests
* MemberTableField needs its own item request class so that it can overload the delete method
*/
function handleItem($request) {
return new MemberTableField_ItemRequest($this, $request->param('ID'));
}
} }
/** /**
@ -460,7 +315,7 @@ class MemberTableField extends ComplexTableField {
class MemberTableField_Popup extends ComplexTableField_Popup { class MemberTableField_Popup extends ComplexTableField_Popup {
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) { function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
$group = ($controller instanceof MemberTableField) ? $controller->getGroup() : $controller->getParent()->getGroup(); $group = ($controller instanceof MemberTableField) ? $controller->getGroup() : $controller->getParentController()->getGroup();
// Set default groups - also implemented in AddForm() // Set default groups - also implemented in AddForm()
if($group) { if($group) {
$groupsField = $fields->dataFieldByName('Groups'); $groupsField = $fields->dataFieldByName('Groups');
@ -513,44 +368,4 @@ class MemberTableField_Item extends ComplexTableField_Item {
} }
} }
/**
* @package cms
* @subpackage security
*/
class MemberTableField_ItemRequest extends ComplexTableField_ItemRequest {
/**
* Deleting an item from a member table field should just remove that member from the group
*/
function delete($request) {
// Protect against CSRF on destructive action
$token = $this->ctf->getForm()->getSecurityToken();
if(!$token->checkRequest($request)) return $this->httpError('400');
if($this->ctf->Can('delete') !== true) {
return false;
}
// if a group limitation is set on the table, remove relation.
// otherwise remove the record from the database
if($this->ctf->getGroup()) {
$groupID = $this->ctf->sourceID();
$group = DataObject::get_by_id('Group', $groupID);
// Remove from group and all child groups
foreach($group->getAllChildren() as $subGroup) {
$this->dataObj()->Groups()->remove($subGroup);
}
$this->dataObj()->Groups()->remove($groupID);
} else {
$this->dataObj()->delete();
}
}
function getParent() {
return $this->ctf;
}
}
?> ?>

View File

@ -305,7 +305,6 @@ MemberFilterButton.prototype = {
updateURL += '&' + this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value ); updateURL += '&' + this.inputFields[index].name + '=' + encodeURIComponent( this.inputFields[index].value );
} }
} }
updateURL += ($('SecurityID') ? '&SecurityID=' + $('SecurityID').value : '');
jQuery($(fieldID)).get(updateURL, null, function() {Behaviour.apply($(fieldID), true);}); jQuery($(fieldID)).get(updateURL, null, function() {Behaviour.apply($(fieldID), true);});
} catch(er) { } catch(er) {

View File

@ -255,7 +255,7 @@ JS;
} }
$pageStart = (isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0; $pageStart = (isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
$sourceItems->setPageLimits($pageStart, $this->pageSize, $this->totalCount); $sourceItems->setPageLimits($pageStart, $this->pageSize, $this->TotalCount());
$output = new DataObjectSet(); $output = new DataObjectSet();
foreach($sourceItems as $pageIndex=>$item) { foreach($sourceItems as $pageIndex=>$item) {
@ -343,85 +343,6 @@ JS;
$this->controller = $controller; $this->controller = $controller;
} }
/**
* Determines on which relation-class the DetailForm is saved
* by looking at the surrounding form-record.
*
* @return String
*/
function getParentClass() {
if($this->parentClass === false) {
// purposely set parent-relation to false
return false;
} elseif(!empty($this->parentClass)) {
return $this->parentClass;
} elseif($this->form && $this->form->getRecord()) {
return $this->form->getRecord()->ClassName;
}
}
/**
* Return the record in which the CTF resides, if it exists.
*/
function getParentRecord() {
if($this->form && $record = $this->form->getRecord()) {
return $record;
} else {
$parentID = (int)$this->sourceID();
$parentClass = $this->getParentClass();
if($parentClass) {
if($parentID) return DataObject::get_by_id($parentClass, $parentID);
else return singleton($parentClass);
}
}
}
/**
* (Optional) Setter for a correct parent-relation-class.
* Defaults to the record loaded into the surrounding form as a fallback.
* Caution: Please use the classname, not the actual column-name in the database.
*
* @param $className string
*/
function setParentClass($className) {
$this->parentClass = $className;
}
/**
* Returns the db-fieldname of the currently used has_one-relationship.
*/
function getParentIdName($parentClass, $childClass) {
return $this->getParentIdNameRelation($childClass, $parentClass, 'has_one');
}
/**
* Manually overwrites the parent-ID relations.
* @see setParentClass()
*
* @param String $str Example: FamilyID (when one Individual has_one Family)
*/
function setParentIdName($str) {
$this->parentIdName = $str;
}
/**
* Returns the db-fieldname of the currently used relationship.
* Note: constructed resolve ambiguous cases in the same manner as
* DataObject::getComponentJoinField()
*/
function getParentIdNameRelation($parentClass, $childClass, $relation) {
if($this->parentIdName) return $this->parentIdName;
$relations = array_flip(singleton($parentClass)->$relation());
$classes = array_reverse(ClassInfo::ancestry($childClass));
foreach($classes as $class) {
if(isset($relations[$class])) return $relations[$class] . 'ID';
}
return false;
}
function setTemplatePopup($template) { function setTemplatePopup($template) {
$this->templatePopup = $template; $this->templatePopup = $template;
} }
@ -458,45 +379,8 @@ JS;
} }
function getFieldsFor($childData) { function getFieldsFor($childData) {
$hasManyRelationName = null;
$manyManyRelationName = null;
// See if our parent class has any many_many relations by this source class
if($parentClass = $this->getParentRecord()) {
$manyManyRelations = $parentClass->many_many();
$manyManyRelationName = null;
$manyManyComponentSet = null;
$hasManyRelations = $parentClass->has_many();
$hasManyRelationName = null;
$hasManyComponentSet = null;
if($manyManyRelations) foreach($manyManyRelations as $relation => $class) {
if($class == $this->sourceClass()) {
$manyManyRelationName = $relation;
}
}
if($hasManyRelations) foreach($hasManyRelations as $relation => $class) {
if($class == $this->sourceClass()) {
$hasManyRelationName = $relation;
}
}
}
// Add the relation value to related records
if(!$childData->ID && $this->getParentClass()) {
// make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
$parentIDName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
$childData->$parentIDName = $this->sourceID();
}
$detailFields = $this->getCustomFieldsFor($childData); $detailFields = $this->getCustomFieldsFor($childData);
if($this->getParentClass() && $hasManyRelationName && $childData->ID) {
$hasManyComponentSet = $parentClass->getComponents($hasManyRelationName);
}
// the ID field confuses the Controller-logic in finding the right view for ReferencedField // the ID field confuses the Controller-logic in finding the right view for ReferencedField
$detailFields->removeByName('ID'); $detailFields->removeByName('ID');
@ -505,9 +389,7 @@ JS;
$detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID)); $detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID));
} }
// add a namespaced ID instead thats "converted" by saveComplexTableField() /* TODO: Figure out how to implement this
$detailFields->push(new HiddenField('ctf[ClassName]', '', $this->sourceClass()));
if($this->getParentClass()) { if($this->getParentClass()) {
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass())); $detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
@ -518,6 +400,7 @@ JS;
$detailFields->push(new HiddenField($parentIdName, '', $this->sourceID())); $detailFields->push(new HiddenField($parentIdName, '', $this->sourceID()));
} }
} }
*/
return $detailFields; return $detailFields;
} }
@ -689,7 +572,7 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
return false; return false;
} }
$this->dataObj()->delete(); $this->ctf->getDataList()->removeByID($this->itemID);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
@ -761,7 +644,7 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
} }
// Save this item into the given relationship // Save this item into the given relationship
$this->ctf->getDataList()->add($childData); $this->ctf->getDataList()->add($dataObject);
$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
@ -798,16 +681,16 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
} }
function PopupLastLink() { function PopupLastLink() {
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) { if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->TotalCount()-1) {
return null; return null;
} }
$start = $this->totalCount - 1; $start = $this->TotalCount - 1;
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}"); return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
} }
function PopupNextLink() { function PopupNextLink() {
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) { if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->TotalCount()-1) {
return null; return null;
} }
@ -836,13 +719,13 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
$result = new DataObjectSet(); $result = new DataObjectSet();
if($currentItem < 6) { if($currentItem < 6) {
$offset = 1; $offset = 1;
} elseif($this->totalCount - $currentItem <= 4) { } elseif($this->TotalCount() - $currentItem <= 4) {
$offset = $currentItem - (10 - ($this->totalCount - $currentItem)); $offset = $currentItem - (10 - ($this->TotalCount() - $currentItem));
$offset = $offset <= 0 ? 1 : $offset; $offset = $offset <= 0 ? 1 : $offset;
} else { } else {
$offset = $currentItem - 5; $offset = $currentItem - 5;
} }
for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->totalCount;$i++) { for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->TotalCount();$i++) {
$start = $i - 1; $start = $i - 1;
$links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}"); $links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}");
$links['number'] = $i; $links['number'] = $i;
@ -863,13 +746,6 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
* ################################# * #################################
*/ */
/**
* Returns the db-fieldname of the currently used has_one-relationship.
*/
function getParentIdName($parentClass, $childClass) {
return $this->getParentIdNameRelation($childClass, $parentClass, 'has_one');
}
/** /**
* Manually overwrites the parent-ID relations. * Manually overwrites the parent-ID relations.
* @see setParentClass() * @see setParentClass()
@ -877,23 +753,7 @@ class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
* @param String $str Example: FamilyID (when one Individual has_one Family) * @param String $str Example: FamilyID (when one Individual has_one Family)
*/ */
function setParentIdName($str) { function setParentIdName($str) {
$this->parentIdName = $str; throw new Exception("setParentIdName is no longer necessary");
}
/**
* Returns the db-fieldname of the currently used relationship.
*/
function getParentIdNameRelation($parentClass, $childClass, $relation) {
if($this->parentIdName) return $this->parentIdName;
$relations = singleton($parentClass)->$relation();
$classes = ClassInfo::ancestry($childClass);
if($relations) {
foreach($relations as $k => $v) {
if(array_key_exists($v, $classes)) return $k . 'ID';
}
}
return false;
} }
function setTemplatePopup($template) { function setTemplatePopup($template) {

View File

@ -134,14 +134,6 @@ class TableListField extends FormField {
*/ */
protected $customCsvQuery; protected $customCsvQuery;
/**
* @var $customSourceItems DataObjectSet Use the manual setting of a result-set only as a last-resort
* for sets which can't be resolved in a single query.
*
* @todo Add pagination support for customSourceItems.
*/
protected $customSourceItems;
/** /**
* Character to seperate exported columns in the CSV file * Character to seperate exported columns in the CSV file
*/ */
@ -166,12 +158,6 @@ class TableListField extends FormField {
"\n"=>"", "\n"=>"",
); );
/**
* @var int Shows total count regardless or pagination
*/
protected $totalCount;
/** /**
* @var boolean Trigger pagination * @var boolean Trigger pagination
*/ */
@ -240,22 +226,29 @@ class TableListField extends FormField {
protected $__cachedQuery; protected $__cachedQuery;
/**
* This is a flag that enables some backward-compatibility helpers.
*/
private $getDataListFromForm;
function __construct($name, $sourceClass = null, $fieldList = null, $sourceFilter = null, function __construct($name, $sourceClass = null, $fieldList = null, $sourceFilter = null,
$sourceSort = null, $sourceJoin = null) { $sourceSort = null, $sourceJoin = null) {
$this->fieldList = ($fieldList) ? $fieldList : singleton($sourceClass)->summaryFields();
if($sourceClass) { if($sourceClass) {
// You can optionally pass a DataList as the 2nd argument to the constructor // You can optionally pass a DataList/DataObjectSet
if($sourceClass instanceof DataList) { if($sourceClass instanceof DataObjectSet) {
$this->dataList = $sourceClass; $this->dataList = $sourceClass;
} else { } else {
$this->dataList = DataObject::get($sourceClass)->filter($sourceFilter) $this->dataList = DataObject::get($sourceClass)->filter($sourceFilter)
->sort($sourceSort)->join($sourceJoin); ->sort($sourceSort)->join($sourceJoin);
// Grab it from the form relation, if available.
$this->getDataListFromForm = true;
} }
} }
$this->fieldList = ($fieldList) ? $fieldList : singleton($this->sourceClass())->summaryFields();
$this->readOnly = false; $this->readOnly = false;
parent::__construct($name); parent::__construct($name);
@ -271,7 +264,10 @@ class TableListField extends FormField {
); );
function sourceClass() { function sourceClass() {
return $this->getDataList()->dataClass(); $list = $this->getDataList();
if(method_exists($list, 'dataClass')) return $list->dataClass();
// Failover for DataObjectSet
else return get_class($list->First());
} }
function handleItem($request) { function handleItem($request) {
@ -358,13 +354,10 @@ JS
* @return bool * @return bool
*/ */
function isFieldSortable($fieldName) { function isFieldSortable($fieldName) {
if($this->customSourceItems || $this->disableSorting) { if($this->disableSorting) return false;
return false; $list = $this->getDataList();
} if(method_exists($list,'canSortBy')) return $list->canSortBy($fieldName);
else return false;
if(!$this->__cachedQuery) $this->__cachedQuery = $this->getQuery();
return $this->__cachedQuery->canSortBy($fieldName);
} }
/** /**
@ -400,45 +393,42 @@ JS
} }
function setCustomSourceItems(DataObjectSet $items) { function setCustomSourceItems(DataObjectSet $items) {
user_error('TableList::setCustomSourceItems() deprecated, just pass the items into the constructor', E_USER_WARNING);
// The type-hinting above doesn't seem to work consistently // The type-hinting above doesn't seem to work consistently
if($items instanceof DataObjectSet) { if($items instanceof DataObjectSet) {
$this->customSourceItems = $items; $this->dataList = $items;
} else { } else {
user_error('TableList::setCustomSourceItems() should be passed a DataObjectSet', E_USER_WARNING); user_error('TableList::setCustomSourceItems() should be passed a DataObjectSet', E_USER_WARNING);
} }
} }
/**
* Get items, with sort & limit applied
*/
function sourceItems() { function sourceItems() {
// get items (this may actually be a DataObjectSet)
$items = clone $this->getDataList();
// TODO: Sorting could be implemented on regular DataObjectSets.
if(method_exists($items,'canSortBy') && isset($_REQUEST['ctf'][$this->Name()]['sort'])) {
$sort = $_REQUEST['ctf'][$this->Name()]['sort'];
// TODO: sort direction
if($items->canSortBy($sort)) $items = $items->sort($sort);
}
// Determine pagination limit, offset // Determine pagination limit, offset
$SQL_limit = ($this->showPagination && $this->pageSize) ? "{$this->pageSize}" : null; // To disable pagination, set $this->showPagination to false.
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) { if($this->showPagination && $this->pageSize) {
$SQL_start = (isset($_REQUEST['ctf'][$this->Name()]['start'])) ? intval($_REQUEST['ctf'][$this->Name()]['start']) : "0"; $SQL_limit = (int)$this->pageSize;
} else { if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
$SQL_start = 0; $SQL_start = (isset($_REQUEST['ctf'][$this->Name()]['start'])) ? intval($_REQUEST['ctf'][$this->Name()]['start']) : "0";
} } else {
$SQL_start = 0;
}
// Custom source items can be explicitly passed $items = $items->getRange($SQL_start, $SQL_limit);
if(isset($this->customSourceItems)) { }
if($this->showPagination && $this->pageSize) {
$items = $this->customSourceItems->getRange($SQL_start, $SQL_limit);
} else {
$items = $this->customSourceItems;
}
// Otherwise we use the internal data list
} else {
// get the DataList of items
$items = $this->getDataList();
// we don't limit when doing certain actions T
$methodName = isset($_REQUEST['url']) ? array_pop(explode('/', $_REQUEST['url'])) : null;
if(!$methodName || !in_array($methodName,array('printall','export'))) {
$items->limit(array(
'limit' => $SQL_limit,
'start' => (isset($SQL_start)) ? $SQL_start : null
));
}
}
return $items; return $items;
} }
@ -458,12 +448,9 @@ JS
* Returns the DataList for this field. * Returns the DataList for this field.
*/ */
function getDataList() { function getDataList() {
// Load the data from the form // If we weren't passed in a DataList to begin with, try and get the datalist from the form
// Note that this will override any specific. This is so that explicitly-passed sets of if($this->form && $this->getDataListFromForm) {
// parameters that represent a relation can be replaced with the relation itself. This is $this->getDataListFromForm = false;
// a little clumsy and won't work if people have used a field name that is the same as a
// relation but have specified alternative parameters.
if($this->form) {
$relation = $this->name; $relation = $this->name;
if($record = $this->form->getRecord()) { if($record = $this->form->getRecord()) {
if($record->hasMethod($relation)) $this->dataList = $record->$relation(); if($record->hasMethod($relation)) $this->dataList = $record->$relation();
@ -474,20 +461,7 @@ JS
user_error(get_class($this). ' is missing a DataList', E_USER_ERROR); user_error(get_class($this). ' is missing a DataList', E_USER_ERROR);
} }
$dl = clone $this->dataList; return $this->dataList;
if(isset($_REQUEST['ctf'][$this->Name()]['sort'])) {
$query = $this->dataList->dataQuery()->query();
$SQL_sort = Convert::raw2sql($_REQUEST['ctf'][$this->Name()]['sort']);
$sql = $query->sql();
// see {isFieldSortable}
if(in_array($SQL_sort,$query->select) || stripos($sql,"AS {$SQL_sort}")) {
$dl->sort($SQL_sort);
}
if($query->canSortBy($column)) $query->orderby = $column.' '.$dir;
}
return $dl;
} }
function getCsvDataList() { function getCsvDataList() {
@ -499,14 +473,20 @@ JS
* @deprecated Use getDataList() instead. * @deprecated Use getDataList() instead.
*/ */
function getQuery() { function getQuery() {
return $this->getDataList()->dataQuery()->query(); $list = $this->getDataList();
if(method_exists($list,'dataQuery')) {
return $this->getDataList()->dataQuery()->query();
}
} }
/** /**
* @deprecated Use getCsvDataList() instead. * @deprecated Use getCsvDataList() instead.
*/ */
function getCsvQuery() { function getCsvQuery() {
return $this->getCsvDataList()->dataQuery()->query(); $list = $this->getCsvDataList();
if(method_exists($list,'dataQuery')) {
return $list->dataQuery()->query();
}
} }
function FieldList() { function FieldList() {
@ -562,8 +542,7 @@ JS
$childId = Convert::raw2sql($_REQUEST['ctf']['childID']); $childId = Convert::raw2sql($_REQUEST['ctf']['childID']);
if (is_numeric($childId)) { if (is_numeric($childId)) {
$childObject = DataObject::get_by_id($this->sourceClass(), $childId); $this->getDataList()->removeById($childId);
if($childObject) $childObject->delete();
} }
// TODO return status in JSON etc. // TODO return status in JSON etc.
@ -884,8 +863,19 @@ JS
} }
} }
/**
* @ignore
*/
private $_cache_TotalCount;
/**
* Return the total number of items in the source DataList
*/
function TotalCount() { function TotalCount() {
return $this->getDataList()->Count(); if($this->_cache_TotalCount === null) {
$this->_cache_TotalCount = $this->getDataList()->Count();
}
return $this->_cache_TotalCount;
} }
@ -951,6 +941,14 @@ JS
$now = Date("d-m-Y-H-i"); $now = Date("d-m-Y-H-i");
$fileName = "export-$now.csv"; $fileName = "export-$now.csv";
// No pagination for export
$oldShowPagination = $this->showPagination;
$this->showPagination = false;
$result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
$this->showPagination = $oldShowPagination;
if($fileData = $this->generateExportFileData($numColumns, $numRows)){ if($fileData = $this->generateExportFileData($numColumns, $numRows)){
return SS_HTTPRequest::send_file($fileData, $fileName); return SS_HTTPRequest::send_file($fileData, $fileName);
}else{ }else{
@ -1562,42 +1560,11 @@ class TableListField_ItemRequest extends RequestHandler {
// used to discover fields if requested and for population of field // used to discover fields if requested and for population of field
if(is_numeric($this->itemID)) { if(is_numeric($this->itemID)) {
// we have to use the basedataclass, otherwise we might exclude other subclasses // we have to use the basedataclass, otherwise we might exclude other subclasses
return DataObject::get_by_id(ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $this->itemID); return $this->ctf->getDataList()->byId($this->itemID);
} }
} }
/**
* Returns the db-fieldname of the currently used has_one-relationship.
*/
function getParentIdName( $parentClass, $childClass ) {
return $this->getParentIdNameRelation( $childClass, $parentClass, 'has_one' );
}
/**
* Manually overwrites the parent-ID relations.
* @see setParentClass()
*
* @param String $str Example: FamilyID (when one Individual has_one Family)
*/
function setParentIdName($str) {
$this->parentIdName = $str;
}
/**
* Returns the db-fieldname of the currently used relationship.
*/
function getParentIdNameRelation($parentClass, $childClass, $relation) {
if($this->parentIdName) return $this->parentIdName;
$relations = singleton($parentClass)->$relation();
$classes = ClassInfo::ancestry($childClass);
foreach($relations as $k => $v) {
if(array_key_exists($v, $classes)) return $k . 'ID';
}
return false;
}
/** /**
* @return TableListField * @return TableListField
*/ */

View File

@ -153,7 +153,6 @@ class Group extends DataObject {
} }
$memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd')); $memberList->setPermissions(array('edit', 'delete', 'export', 'add', 'inlineadd'));
$memberList->setParentClass('Group');
$memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User')); $memberList->setPopupCaption(_t('SecurityAdmin.VIEWUSER', 'View User'));
$fields->push($idField = new HiddenField("ID")); $fields->push($idField = new HiddenField("ID"));

View File

@ -100,13 +100,11 @@ class ComplexTableFieldTest_Controller extends Controller {
$playersField = new ComplexTableField( $playersField = new ComplexTableField(
$this, $this,
'Players', 'Players',
'ComplexTableFieldTest_Player', $team->Players(),
ComplexTableFieldTest_Player::$summary_fields, ComplexTableFieldTest_Player::$summary_fields,
'getCMSFields' 'getCMSFields'
); );
$playersField->setParentClass('ComplexTableFieldTest_Team');
$form = new Form( $form = new Form(
$this, $this,
'ManyManyForm', 'ManyManyForm',
@ -131,13 +129,11 @@ class ComplexTableFieldTest_Controller extends Controller {
$sponsorsField = new ComplexTableField( $sponsorsField = new ComplexTableField(
$this, $this,
'Sponsors', 'Sponsors',
'ComplexTableFieldTest_Sponsor', $team->Sponsors(),
ComplexTableFieldTest_Sponsor::$summary_fields, ComplexTableFieldTest_Sponsor::$summary_fields,
'getCMSFields' 'getCMSFields'
); );
$sponsorsField->setParentClass('ComplexTableFieldTest_Team');
$form = new Form( $form = new Form(
$this, $this,
'HasManyForm', 'HasManyForm',

View File

@ -292,6 +292,38 @@ class TableListFieldTest extends SapphireTest {
unset($_REQUEST['ctf']); unset($_REQUEST['ctf']);
} }
/**
* Check that a DataObjectSet can be passed to TableListField
*/
function testDataObjectSet() {
$one = new TableListFieldTest_Obj;
$one->A = "A-one";
$two = new TableListFieldTest_Obj;
$two->A = "A-two";
$three = new TableListFieldTest_Obj;
$three->A = "A-three";
$list = new DataObjectSet($one, $two, $three);
// A TableListField must be inside a form for its links to be generated
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
new TableListField("Tester", $list, array(
"A" => "Col A",
"B" => "Col B",
"C" => "Col C",
"D" => "Col D",
"E" => "Col E",
))
), new FieldSet());
$table = $form->dataFieldByName('Tester');
$rendered = $table->FieldHolder();
$this->assertContains('A-one', $rendered);
$this->assertContains('A-two', $rendered);
$this->assertContains('A-three', $rendered);
}
} }
class TableListFieldTest_Obj extends DataObject implements TestOnly { class TableListFieldTest_Obj extends DataObject implements TestOnly {