2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides a tabuar list in your form with view, edit and add links to edit records
|
|
|
|
* with a "has-one"-relationship. Detail-views are shown in a greybox-iframe.
|
|
|
|
* Features pagination in the overview as well as the detail-views.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
|
|
|
* CAUTION: You need to make sure that the original form-call to the main controller (e.g. EditForm())
|
|
|
|
* returns a form which includes this field even if no data is loaded,
|
2007-07-19 12:40:28 +02:00
|
|
|
* to provide a "starting point" for action_callfieldmethod and ReferencedField.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* All URL data sent to and from ComplexTableField is encapsulated in $_REQUEST['ctf']
|
|
|
|
* to avoid side-effects with the main controller.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* Example-URL for a "DetailForm"-call explained:
|
|
|
|
* "/admin/family/?executeForm=EditForm&action_callfieldmethod&fieldName=Individual&childID=7&methodName=edit"
|
|
|
|
* - executeForm Name of the form on the main rendering page (e.g. "FamilyAdmin")
|
|
|
|
* - action_callfieldmethod Trigger to call a method of a single field in "EditForm" instead of rendering the whole thing
|
|
|
|
* - fieldName Name of the targeted formField
|
|
|
|
* - methodName Method on the formfield (e.g. "ComplexTableField")
|
2007-09-15 23:36:17 +02:00
|
|
|
* - childID Identifier of the database-record (the targeted table is determined by the $sourceClass parameter)
|
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* TODO Find a less fragile solution for accessing this field through the main controller and ReferencedField, e.g.
|
|
|
|
* build a seperate CTF-instance (doesn't necessarly have to be connected to the original by ReferencedField)
|
|
|
|
* TODO Control width/height of popup by constructor (hardcoded at the moment)
|
|
|
|
* TODO Integrate search from MemberTableField.php
|
|
|
|
* TODO Less performance-hungry implementation of detail-view paging (don't return all items on a single view)
|
|
|
|
* TODO Use automatic has-many and many-many functions to return a ComponentSet rather than building the join manually
|
|
|
|
*/
|
|
|
|
class ComplexTableField extends TableListField {
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
protected $detailFormFields, $viewAction, $sourceJoin, $sourceItems, $unpagedSourceItems;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @var Controller
|
|
|
|
*/
|
|
|
|
protected $controller;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @var string Classname of the parent-relation to correctly link new records.
|
|
|
|
*/
|
|
|
|
protected $parentClass;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @var array Influence output without having to subclass the template.
|
|
|
|
*/
|
|
|
|
protected $permissions = array(
|
|
|
|
"add",
|
|
|
|
"edit",
|
|
|
|
"show",
|
|
|
|
"delete",
|
|
|
|
//"export",
|
|
|
|
);
|
|
|
|
|
2007-09-04 04:27:55 +02:00
|
|
|
protected $itemClass = 'ComplexTableField_Item';
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Template-Overrides
|
|
|
|
*/
|
|
|
|
protected $template = "ComplexTableField";
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
protected $templatePopup = "ComplexTableField_popup";
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @var boolean Trigger pagination (defaults to true for ComplexTableField)
|
|
|
|
*/
|
|
|
|
protected $showPagination = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Caption the popup will show (defaults to the selected action).
|
|
|
|
* This is set by javascript and used by greybox.
|
|
|
|
*/
|
|
|
|
protected $popupCaption = null;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @var $detailFormValidator Validator
|
|
|
|
*/
|
|
|
|
protected $detailFormValidator = null;
|
|
|
|
|
2007-12-02 22:27:34 +01:00
|
|
|
protected $relationAutoSetting = true;
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* See class comments
|
|
|
|
*
|
|
|
|
* @param ContentController $controller
|
|
|
|
* @param string $name
|
|
|
|
* @param string $sourceClass
|
|
|
|
* @param array $fieldList
|
|
|
|
* @param FieldSet $detailFormFields
|
|
|
|
* @param string $sourceFilter
|
|
|
|
* @param string $sourceSort
|
|
|
|
* @param string $sourceJoin
|
|
|
|
*/
|
|
|
|
function __construct($controller, $name, $sourceClass, $fieldList, $detailFormFields = null, $sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
|
|
|
|
$this->detailFormFields = $detailFormFields;
|
|
|
|
$this->controller = $controller;
|
|
|
|
$this->pageSize = 10;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
Requirements::javascript("jsparty/greybox/AmiJS.js");
|
|
|
|
Requirements::javascript("jsparty/greybox/greybox.js");
|
|
|
|
Requirements::javascript('sapphire/javascript/TableListField.js');
|
|
|
|
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
|
|
|
|
Requirements::css("jsparty/greybox/greybox.css");
|
|
|
|
Requirements::css("sapphire/css/ComplexTableField.css");
|
2007-12-02 22:27:34 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
|
2007-12-02 22:27:34 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function isComposite() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function FieldHolder() {
|
|
|
|
// set caption if required
|
|
|
|
if($this->popupCaption) {
|
|
|
|
$id = $this->id();
|
|
|
|
if(Director::is_ajax()) {
|
|
|
|
$js = <<<JS
|
|
|
|
$('$id').GB_Caption = '$this->popupCaption';
|
|
|
|
JS;
|
|
|
|
FormResponse::add($js);
|
|
|
|
} else {
|
|
|
|
$js = <<<JS
|
|
|
|
Event.observe(window, 'load', function() { \$('$id').GB_Caption = '$this->popupCaption'; });
|
|
|
|
JS;
|
|
|
|
Requirements::customScript($js);
|
|
|
|
}
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// compute sourceItems here instead of Items() to ensure that
|
|
|
|
// pagination and filters are respected on template accessors
|
|
|
|
$this->sourceItems();
|
|
|
|
|
|
|
|
return $this->renderWith($this->template);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Returns non-paginated items.
|
|
|
|
* Please use Items() for pagination.
|
|
|
|
* This function is called whenever a complete result-set is needed,
|
|
|
|
* so even if a single record is displayed in a popup, we need the results
|
|
|
|
* to make pagination work.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* TODO Merge with more efficient querying of TableListField
|
|
|
|
*/
|
|
|
|
function sourceItems() {
|
|
|
|
if($this->sourceItems) {
|
|
|
|
return $this->sourceItems;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$limitClause = "";
|
|
|
|
if($this->pageSize) {
|
|
|
|
$limitClause = "{$this->pageSize}";
|
|
|
|
} else {
|
|
|
|
$limitClause = "0";
|
|
|
|
}
|
|
|
|
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
|
|
|
|
$SQL_start = intval($_REQUEST['ctf'][$this->Name()]['start']);
|
|
|
|
$limitClause .= " OFFSET {$SQL_start}";
|
|
|
|
}
|
|
|
|
|
|
|
|
$sourceItems = DataObject::get($this->sourceClass, $this->sourceFilter, $this->sourceSort, $this->sourceJoin, $limitClause);
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->unpagedSourceItems = DataObject::get($this->sourceClass, $this->sourceFilter, $this->sourceSort, $this->sourceJoin);
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->totalCount = ($this->unpagedSourceItems) ? $this->unpagedSourceItems->TotalItems() : null;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return $sourceItems;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @return DataObjectSet
|
|
|
|
*/
|
|
|
|
function Items() {
|
|
|
|
$this->sourceItems = $this->sourceItems();
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if(!$this->sourceItems) {
|
|
|
|
return null;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$pageStart = (isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
|
|
|
|
$this->sourceItems->setPageLimits($pageStart, $this->pageSize, $this->totalCount);
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$output = new DataObjectSet();
|
|
|
|
foreach($this->sourceItems as $pageIndex=>$item) {
|
2007-09-04 04:27:55 +02:00
|
|
|
$output->push(Object::create($this->itemClass,$item, $this, $pageStart+$pageIndex));
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
return $output;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Sets the popup-title by javascript. Make sure to use FormResponse in ajax-requests,
|
|
|
|
* otherwise the title-change will only take effect on items existing during page-load.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @param $caption String
|
|
|
|
*/
|
|
|
|
function setPopupCaption($caption) {
|
2007-09-15 23:36:17 +02:00
|
|
|
$this->popupCaption = Convert::raw2js($caption);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Renders view, edit and add, depending on the given information.
|
|
|
|
* The form needs several parameters to function independently of its "parent-form", some derived from the context into a hidden-field,
|
|
|
|
* some derived from the parent context (which is not accessible here) and delivered by GET:
|
|
|
|
* ID, Identifier of the currently edited record (only if record is loaded).
|
|
|
|
* <parentIDName>, Link back to the correct parent record (e.g. "parentID").
|
|
|
|
* parentClass, Link back to correct container-class (the parent-record might have many 'has-one'-relationships)
|
|
|
|
* CAUTION: "ID" in the DetailForm would be the "childID" in the overview table.
|
|
|
|
*/
|
|
|
|
function DetailForm() {
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// Get all the requests
|
2007-08-13 00:47:31 +02:00
|
|
|
$ID = isset($_REQUEST['ctf']['ID']) ? Convert::raw2xml($_REQUEST['ctf']['ID']) : null;
|
2007-09-14 04:04:30 +02:00
|
|
|
$childID = isset($_REQUEST['ctf']['childID']) ? Convert::raw2xml($_REQUEST['ctf']['childID']) : null;
|
2007-07-19 12:40:28 +02:00
|
|
|
$childClass = Convert::raw2xml($_REQUEST['fieldName']);
|
2007-08-13 00:47:31 +02:00
|
|
|
$this->methodName = isset($_REQUEST['methodName']) ? $_REQUEST['methodName'] : null;
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
// used to discover fields if requested and for population of field
|
|
|
|
if(is_numeric($childID)) {
|
|
|
|
$childData = DataObject::get_by_id($this->sourceClass, $childID);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the fieldset is passed, use it, else use the formfields returned
|
|
|
|
// from the object via a string method call.
|
|
|
|
if(is_a($this->detailFormFields,"Fieldset")){
|
|
|
|
$detailFields = $this->detailFormFields;
|
2007-09-14 04:04:30 +02:00
|
|
|
} else if( isset( $childData ) && is_string($this->detailFormFields)){
|
2007-07-19 12:40:28 +02:00
|
|
|
$functioncall = $this->detailFormFields;
|
|
|
|
if($childData->hasMethod($functioncall)){
|
|
|
|
$detailFields = $childData->$functioncall();
|
|
|
|
}
|
2007-09-14 04:04:30 +02:00
|
|
|
} elseif(! isset( $childData ) || $this->methodName == 'add') {
|
2007-07-19 12:40:28 +02:00
|
|
|
$SNG_sourceClass = singleton($this->sourceClass);
|
|
|
|
if(is_numeric($ID) && $this->getParentClass()) {
|
|
|
|
// make sure the relation-link is existing, even if we just add the sourceClass
|
|
|
|
// and didn't save it
|
2007-09-04 04:27:55 +02:00
|
|
|
$parentIDName = $this->getParentIdName( $this->getParentClass(), $this->sourceClass );
|
2007-07-19 12:40:28 +02:00
|
|
|
$SNG_sourceClass->$parentIDName = $ID;
|
|
|
|
}
|
2007-09-04 04:27:55 +02:00
|
|
|
$functioncall = $this->detailFormFields;
|
|
|
|
if($SNG_sourceClass->hasMethod($functioncall)){
|
|
|
|
$detailFields = $SNG_sourceClass->$functioncall();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
$detailFields = $SNG_sourceClass->getCMSFields();
|
2007-07-19 12:40:28 +02:00
|
|
|
} else {
|
|
|
|
$detailFields = $childData->getCMSFields();
|
|
|
|
}
|
|
|
|
if($this->getParentClass()) {
|
2007-11-23 02:10:19 +01:00
|
|
|
$parentIdName = $this->getParentIdName($this->sourceClass,$this->getParentClass());
|
2007-07-19 12:40:28 +02:00
|
|
|
if(!$parentIdName) {
|
|
|
|
user_error("ComplexTableField::DetailForm() Cannot automatically
|
|
|
|
determine 'has-one'-relationship to parent,
|
|
|
|
please use setParentClass() to set it manually",
|
|
|
|
E_USER_WARNING);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// add relational fields
|
|
|
|
$detailFields->push(new HiddenField("ctf[parentClass]"," ",$this->getParentClass()));
|
2007-12-02 22:27:34 +01:00
|
|
|
if( $this->relationAutoSetting )
|
|
|
|
$detailFields->push(new HiddenField("$parentIdName"," ",$ID));
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
|
|
|
|
$detailFields->removeByName('ID');
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-11-23 02:10:19 +01:00
|
|
|
// only add childID if we're not adding a record
|
|
|
|
if($this->methodName != 'add') {
|
|
|
|
$detailFields->push(new HiddenField("ctf[childID]","",$childID));
|
|
|
|
}
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
// add a namespaced ID instead thats "converted" by saveComplexTableField()
|
|
|
|
$detailFields->push(new HiddenField("ctf[ClassName]","",$this->sourceClass));
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$readonly = ($this->methodName == "show");
|
|
|
|
|
|
|
|
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
|
|
|
|
if(!isset($this->detailFormValidator) && singleton($this->sourceClass)->hasMethod('getValidator')) {
|
|
|
|
$this->detailFormValidator = singleton($this->sourceClass)->getValidator();
|
|
|
|
}
|
|
|
|
|
|
|
|
$form = Object::create('ComplexTableField_Popup',$this, "DetailForm", $detailFields, $this->sourceClass, $readonly, $this->detailFormValidator);
|
|
|
|
|
|
|
|
if (is_numeric($childID)) {
|
|
|
|
if ($this->methodName == "show" || $this->methodName == "edit") {
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$form->loadDataFrom($childData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->methodName == "show") {
|
|
|
|
$form->makeReadonly();
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return $form;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @param $validator Validator
|
|
|
|
*/
|
|
|
|
function setDetailFormValidator( Validator $validator ) {
|
|
|
|
$this->detailFormValidator = $validator;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the content of this formfield without surrounding layout. Triggered by Javascript
|
|
|
|
* to update content after a DetailForm-save-action.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function ajax_render() {
|
|
|
|
return $this->renderWith($this->template);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Just a hook, processed in {DetailForm()}
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function show() {
|
|
|
|
if($this->Can('show') !== true) {
|
|
|
|
return false;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->methodName = "edit";
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->sourceItems = $this->sourceItems();
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->pageSize = 1;
|
|
|
|
|
|
|
|
if(isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) {
|
|
|
|
$this->unpagedSourceItems->setPageLimits($_REQUEST['ctf'][$this->Name()]['start'], $this->pageSize, $this->totalCount);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
echo $this->renderWith($this->templatePopup);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Just a hook, processed in {DetailForm()}
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function edit() {
|
|
|
|
if($this->Can('edit') !== true) {
|
|
|
|
return false;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->methodName = "edit";
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->sourceItems = $this->sourceItems();
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->pageSize = 1;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if(is_numeric($_REQUEST['ctf']['start'])) {
|
|
|
|
$this->unpagedSourceItems->setPageLimits($_REQUEST['ctf']['start'], $this->pageSize, $this->totalCount);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
echo $this->renderWith($this->templatePopup);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Just a hook, processed in {DetailForm()}
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function add() {
|
|
|
|
if($this->Can('add') !== true) {
|
|
|
|
return false;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->methodName = "add";
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
echo $this->renderWith($this->templatePopup);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates the number of columns needed for colspans
|
|
|
|
* used in template
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return Int
|
|
|
|
*/
|
|
|
|
function ItemCount() {
|
|
|
|
return count($this->fieldList);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Used to toggle paging (makes no sense when adding a record)
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return Boolean
|
|
|
|
*/
|
|
|
|
function IsAddMode() {
|
|
|
|
return ($this->methodName == "add");
|
|
|
|
}
|
|
|
|
|
|
|
|
function sourceID() {
|
|
|
|
$idField = $this->form->dataFieldByName('ID');
|
|
|
|
if(!$idField) {
|
|
|
|
user_error("ComplexTableField needs a formfield named 'ID' to be present", E_USER_ERROR);
|
|
|
|
}
|
|
|
|
// because action_callfieldmethod never actually loads data into the form,
|
|
|
|
// we can't rely on $idField being populated, and fall back to the request-params.
|
|
|
|
// this is a workaround for a bug where each subsequent popup-call didn't have ID
|
|
|
|
// of the parent set, and so didn't properly save the relation
|
2007-11-25 10:55:13 +01:00
|
|
|
return ($idField->Value()) ? $idField->Value() : (isset($_REQUEST['ctf']['ID']) ? $_REQUEST['ctf']['ID'] : null);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* #################################
|
|
|
|
* Pagination
|
|
|
|
* #################################
|
|
|
|
*/
|
|
|
|
function PopupBaseLink() {
|
2007-11-23 02:10:19 +01:00
|
|
|
$link = $this->FormAction() . "&action_callfieldmethod&fieldName={$this->Name()}";
|
|
|
|
if(!strpos($link,'ctf[ID]')) {
|
|
|
|
$link = HTTP::setGetVar('ctf[ID]',$this->sourceID(),$link);
|
|
|
|
}
|
|
|
|
return $link;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function PopupCurrentItem() {
|
|
|
|
return $_REQUEST['ctf']['start']+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function PopupFirstLink() {
|
|
|
|
if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$item = $this->unpagedSourceItems->First();
|
|
|
|
$start = 0;
|
|
|
|
return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function PopupLastLink() {
|
|
|
|
if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
|
|
|
|
return null;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$item = $this->unpagedSourceItems->Last();
|
|
|
|
$start = $this->totalCount - 1;
|
|
|
|
return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function PopupNextLink() {
|
|
|
|
if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
|
|
|
|
return null;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$item = $this->unpagedSourceItems->getOffset($_REQUEST['ctf']['start'] + 1);
|
|
|
|
|
|
|
|
$start = $_REQUEST['ctf']['start'] + 1;
|
|
|
|
return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
|
|
|
|
}
|
|
|
|
|
|
|
|
function PopupPrevLink() {
|
|
|
|
if(!is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$item = $this->unpagedSourceItems->getOffset($_REQUEST['ctf']['start'] - 1);
|
|
|
|
|
|
|
|
$start = $_REQUEST['ctf']['start'] - 1;
|
|
|
|
return $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
|
|
|
|
}
|
2007-09-14 05:12:21 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Method handles pagination in asset popup.
|
|
|
|
*
|
|
|
|
* @return Object DataObjectSet
|
|
|
|
*/
|
|
|
|
|
2007-11-05 02:38:25 +01:00
|
|
|
function Pagination() {
|
2007-11-02 03:53:53 +01:00
|
|
|
$this->pageSize = 9;
|
2007-09-14 05:12:21 +02:00
|
|
|
$currentItem = $this->PopupCurrentItem();
|
|
|
|
$result = new DataObjectSet();
|
|
|
|
if($currentItem < 6) {
|
|
|
|
$offset = 1;
|
|
|
|
} elseif($this->totalCount - $currentItem <= 4) {
|
|
|
|
$offset = $currentItem - (10 - ($this->totalCount - $currentItem));
|
|
|
|
$offset = $offset <= 0 ? 1 : $offset;
|
|
|
|
} else {
|
|
|
|
$offset = $currentItem - 5;
|
|
|
|
}
|
|
|
|
for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->totalCount;$i++) {
|
|
|
|
$start = $i - 1;
|
|
|
|
$item = $this->unpagedSourceItems->getOffset($i-1);
|
|
|
|
$links['link'] = $this->PopupBaseLink() . "&methodName={$_REQUEST['methodName']}&ctf[childID]={$item->ID}&ctf[start]={$start}";
|
|
|
|
$links['number'] = $i;
|
|
|
|
$links['active'] = $i == $currentItem ? false : true;
|
|
|
|
$result->push(new ArrayData($links));
|
|
|
|
}
|
|
|
|
return $result;
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2007-09-15 23:36:17 +02:00
|
|
|
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* #################################
|
|
|
|
* Utilty
|
|
|
|
* #################################
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get part of class ancestry (even if popup is not subclassed it might be styled differently in css)
|
|
|
|
*/
|
|
|
|
function PopupClasses() {
|
|
|
|
global $_ALL_CLASSES;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$items = array();
|
|
|
|
$parents = $_ALL_CLASSES['parents'][$this->class];
|
|
|
|
foreach($parents as $parent) {
|
|
|
|
if(!in_array($parent,$_ALL_CLASSES['parents']["TableListField"])) $items[] = $parent . "_Popup";
|
|
|
|
}
|
|
|
|
$items[] = $this->class . "_Popup";
|
|
|
|
|
|
|
|
return implode(" ", $items);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function AddLink() {
|
|
|
|
return "{$this->PopupBaseLink()}&methodName=add";
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* @return FieldSet
|
|
|
|
*/
|
|
|
|
function createFieldSet() {
|
|
|
|
$fieldset = new FieldSet();
|
|
|
|
foreach($this->fieldTypes as $key => $fieldType){
|
|
|
|
$fieldset->push(new $fieldType($key));
|
|
|
|
}
|
|
|
|
return $fieldset;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Determines on which relation-class the DetailForm is saved
|
|
|
|
* by looking at the surrounding form-record.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
function getParentClass() {
|
|
|
|
if($this->parentClass === false) {
|
|
|
|
// purposely set parent-relation to false
|
|
|
|
return false;
|
|
|
|
} elseif(!empty($this->parentClass)) {
|
|
|
|
return $this->parentClass;
|
|
|
|
} else {
|
|
|
|
return $this->form->getRecord()->ClassName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (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.
|
2007-09-15 23:36:17 +02:00
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @param $className string
|
|
|
|
*/
|
|
|
|
function setParentClass($className) {
|
|
|
|
$this->parentClass = $className;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
2007-09-04 04:27:55 +02:00
|
|
|
* Returns the db-fieldname of the currently used has_one-relationship.
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2007-09-04 04:27:55 +02:00
|
|
|
function getParentIdName( $parentClass, $childClass ) {
|
2007-12-02 22:27:34 +01:00
|
|
|
return $this->getParentIdNameRelation( $childClass, $parentClass, 'has_one' );
|
2007-09-04 04:27:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the db-fieldname of the currently used relationship.
|
|
|
|
*/
|
|
|
|
function getParentIdNameRelation( $parentClass, $childClass, $relation ){
|
|
|
|
$relations = singleton( $parentClass )->$relation();
|
|
|
|
$classes = ClassInfo::ancestry( $childClass );
|
|
|
|
foreach( $relations as $k => $v ) {
|
|
|
|
if( $v == $childClass )
|
|
|
|
return $k . 'ID';
|
|
|
|
else if( array_key_exists( $v, $classes ) )
|
|
|
|
return $classes[ $v ] . 'ID';
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function setTemplatePopup($template) {
|
|
|
|
$this->templatePopup = $template;
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class ComplexTableField_Item extends TableListField_Item {
|
|
|
|
/**
|
|
|
|
* Needed to transfer pagination-status from overview.
|
|
|
|
*/
|
|
|
|
protected $start;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function __construct(DataObject $item, ComplexTableField $parent, $start) {
|
|
|
|
$this->start = $start;
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
parent::__construct($item, $parent);
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function PopupBaseLink() {
|
|
|
|
return $this->parent->FormAction() . "&action_callfieldmethod&fieldName={$this->parent->Name()}&ctf[childID]={$this->item->ID}&ctf[ID]={$this->parent->sourceID()}&ctf[start]={$this->start}";
|
|
|
|
}
|
|
|
|
|
|
|
|
function EditLink() {
|
|
|
|
return $this->PopupBaseLink() . "&methodName=edit";
|
|
|
|
}
|
|
|
|
|
|
|
|
function ShowLink() {
|
|
|
|
return $this->PopupBaseLink() . "&methodName=show";
|
|
|
|
}
|
|
|
|
|
|
|
|
function DeleteLink() {
|
|
|
|
return $this->PopupBaseLink() . "&methodName=delete";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* ComplexTablefield_popup is rendered with a lightbox and can load a more
|
2007-09-15 23:36:17 +02:00
|
|
|
* detailed view of the source class your presenting.
|
2007-07-19 12:40:28 +02:00
|
|
|
* You can customise the fields and requirements as well as any
|
|
|
|
* permissions you might need.
|
|
|
|
*/
|
|
|
|
class ComplexTableField_Popup extends Form {
|
|
|
|
protected $sourceClass;
|
|
|
|
|
|
|
|
function __construct($controller, $name, $field, $sourceClass, $readonly=false, $validator = null) {
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
|
2007-09-15 23:36:17 +02:00
|
|
|
* Some have special requirements.
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
Requirements::clear();
|
|
|
|
//Requirements::css('cms/css/layout.css');
|
|
|
|
Requirements::css('jsparty/tabstrip/tabstrip.css');
|
2007-09-27 22:56:55 +02:00
|
|
|
Requirements::css('sapphire/css/Form.css');
|
2007-07-19 12:40:28 +02:00
|
|
|
Requirements::css('sapphire/css/ComplexTableField_popup.css');
|
2007-11-02 04:01:40 +01:00
|
|
|
Requirements::css('cms/css/typography.css');
|
2007-11-07 05:16:05 +01:00
|
|
|
Requirements::css('cms/css/cms_right.css');
|
2007-07-19 12:40:28 +02:00
|
|
|
Requirements::javascript("jsparty/prototype.js");
|
|
|
|
Requirements::javascript("jsparty/behaviour.js");
|
|
|
|
Requirements::javascript("jsparty/prototype_improvements.js");
|
|
|
|
Requirements::javascript("jsparty/loader.js");
|
|
|
|
Requirements::javascript("jsparty/tabstrip/tabstrip.js");
|
|
|
|
Requirements::javascript("jsparty/scriptaculous/scriptaculous.js");
|
|
|
|
Requirements::javascript("jsparty/scriptaculous/controls.js");
|
|
|
|
Requirements::javascript("jsparty/layout_helpers.js");
|
|
|
|
Requirements::javascript("cms/javascript/LeftAndMain.js");
|
|
|
|
Requirements::javascript("cms/javascript/LeftAndMain_right.js");
|
|
|
|
Requirements::javascript("sapphire/javascript/TableField.js");
|
|
|
|
Requirements::javascript("sapphire/javascript/ComplexTableField.js");
|
|
|
|
Requirements::javascript("sapphire/javascript/ComplexTableField_popup.js");
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->sourceClass = $sourceClass;
|
|
|
|
if(singleton($sourceClass)->hasMethod('getRequirementsForPopup')){
|
|
|
|
singleton($sourceClass)->getRequirementsForPopup();
|
|
|
|
}
|
|
|
|
|
|
|
|
$actions = new FieldSet();
|
|
|
|
if(!$readonly) {
|
|
|
|
$actions->push(
|
|
|
|
$saveAction = new FormAction("saveComplexTableField", "Save")
|
|
|
|
);
|
|
|
|
$saveAction->addExtraClass('save');
|
|
|
|
}
|
|
|
|
|
|
|
|
parent::__construct($controller, $name, $field, $actions, $validator);
|
|
|
|
}
|
|
|
|
|
|
|
|
function FieldHolder() {
|
|
|
|
return $this->renderWith('ComplexTableField_Form');
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
function ShowPagination() {
|
|
|
|
return $this->controller->ShowPagination();
|
|
|
|
}
|
2007-09-15 23:36:17 +02:00
|
|
|
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
|
|
|
* Use the URL-Parameter "action_saveComplexTableField"
|
|
|
|
* to provide a clue to the main controller if the main form has to be rendered,
|
|
|
|
* even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
|
2007-09-15 23:36:17 +02:00
|
|
|
* which in turn saves the record.
|
|
|
|
*
|
2007-07-19 12:40:28 +02:00
|
|
|
* @see {Form::ReferencedField}).
|
|
|
|
*/
|
|
|
|
function saveComplexTableField() {
|
|
|
|
$id = Convert::raw2sql($_REQUEST['ctf']['childID']);
|
2007-09-15 23:36:17 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
if (is_numeric($id)) {
|
|
|
|
$childObject = DataObject::get_by_id($this->sourceClass, $id);
|
|
|
|
} else {
|
|
|
|
$childObject = new $this->sourceClass();
|
2007-11-23 02:10:19 +01:00
|
|
|
$this->fields->removeByName('ID');
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2007-12-02 22:27:34 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->saveInto($childObject);
|
|
|
|
$childObject->write();
|
|
|
|
|
|
|
|
// if ajax-call in an iframe, close window by javascript, else redirect to referrer
|
|
|
|
if(!$_REQUEST['ajax']) {
|
|
|
|
Director::redirect(substr($_SERVER['REQUEST_URI'],0,strpos($_SERVER['REQUEST_URI'],"?")));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
?>
|