Webylon 3.1 API Docs
  • Package
  • Class
  • Tree
  • Deprecated
  • Download
Version: current
  • 3.2
  • 3.1

Packages

  • auth
  • Booking
  • cart
    • shipping
    • steppedcheckout
  • Catalog
  • cms
    • assets
    • batchaction
    • batchactions
    • bulkloading
    • comments
    • content
    • core
    • export
    • newsletter
    • publishers
    • reports
    • security
    • tasks
  • Dashboard
  • DataObjectManager
  • event
  • faq
  • forms
    • actions
    • core
    • fields-basic
    • fields-dataless
    • fields-datetime
    • fields-files
    • fields-formatted
    • fields-formattedinput
    • fields-relational
    • fields-structural
    • transformations
    • validators
  • googlesitemaps
  • guestbook
  • installer
  • newsletter
  • None
  • photo
    • gallery
  • PHP
  • polls
  • recaptcha
  • sapphire
    • api
    • bulkloading
    • control
    • core
    • cron
    • dev
    • email
    • fields-formattedinput
    • filesystem
    • formatters
    • forms
    • i18n
    • integration
    • misc
    • model
    • parsers
    • search
    • security
    • tasks
    • testing
    • tools
    • validation
    • view
    • widgets
  • seo
    • open
      • graph
  • sfDateTimePlugin
  • spamprotection
  • stealth
    • captha
  • subsites
  • userform
    • pagetypes
  • userforms
  • webylon
  • widgets

Classes

  • AssetManager
  • CartTableListField_Item
  • ComplexTableField
  • ComplexTableField_Item
  • ComplexTableField_ItemRequest
  • ComplexTableField_Popup
  • CountryDropdownField
  • DataObjectManager
  • DataObjectManager_Item
  • DataObjectManager_ItemRequest
  • DocumentPageFiles_Manager
  • FileDataObjectManager
  • FileDataObjectManager_Item
  • FileDataObjectManager_ItemRequest
  • HasManyComplexTableField
  • HasManyComplexTableField_Item
  • HasManyDataObjectManager
  • HasManyDataObjectManager_Item
  • HasManyFileDataObjectManager
  • HasManyFileDataObjectManager_Item
  • HasOneComplexTableField
  • HasOneComplexTableField_Item
  • HasOneDataObjectManager
  • HasOneDataObjectManager_Item
  • HasOneFileDataObjectManager
  • HasOneFileDataObjectManager_Item
  • ImageAssetManager
  • ImageDataObjectManager
  • ImageDataObjectManager_Item
  • ImageDataObjectManager_ItemRequest
  • LanguageDropdownField
  • ManyManyComplexTableField
  • ManyManyComplexTableField_Item
  • ManyManyDataObjectManager
  • ManyManyDataObjectManager_Item
  • ManyManyFileDataObjectManager
  • ManyManyFileDataObjectManager_Item
  • Mediaweb3DPageFiles_Manager
  • MediawebPageFiles_Manager
  • MediawebPagePhoto_Manager
  • MediawebPageTexture_Manager
  • PhotoAlbumManager
  • ScaffoldingComplexTableField_Popup
  • SubpageListField_Item
  • SubPageListField_ItemRequest
  • SubsiteAgnosticTableListField
  • TableField
  • TableField_Item
  • TableListField
  • TableListField_Item
  • TableListField_ItemRequest
  • TreeDropdownField
  • TreeDropdownField_Readonly
  • TreeMultiselectField
  • TreeMultiselectField_Readonly
  • TreeSelectorField
   1 <?php
   2 /**
   3  * Provides a tabuar list in your form with view, edit and add links to edit records
   4  * with a "has-one"-relationship. Detail-views are shown in a greybox-iframe.
   5  * Features pagination in the overview as well as the detail-views.
   6  *
   7  * CAUTION: You need to make sure that the original form-call to the main controller (e.g. EditForm())
   8  * returns a form which includes this field even if no data is loaded,
   9  * to provide a "starting point" for action_callfieldmethod and ReferencedField.
  10  *
  11  * All URL data sent to and from ComplexTableField is encapsulated in $_REQUEST['ctf']
  12  * to avoid side-effects with the main controller.
  13  *
  14  * Example-URL for a "DetailForm"-call explained:
  15  * "/admin/family/?executeForm=EditForm&action_callfieldmethod&fieldName=Individual&childID=7&methodName=edit"
  16  *  - executeForm           Name of the form on the main rendering page (e.g. "FamilyAdmin")
  17  *  - action_callfieldmethod    Trigger to call a method of a single field in "EditForm" instead of rendering the whole thing
  18  *  - fieldName             Name of the targeted formField
  19  *  - methodName                Method on the formfield (e.g. "ComplexTableField")
  20  *  - childID               Identifier of the database-record (the targeted table is determined by the $sourceClass parameter)
  21  *
  22  * @todo Find a less fragile solution for accessing this field through the main controller and ReferencedField, e.g.
  23  *      build a seperate CTF-instance (doesn't necessarly have to be connected to the original by ReferencedField)
  24  * @todo Control width/height of popup by constructor (hardcoded at the moment)
  25  * @todo Integrate search from MemberTableField.php
  26  * @todo Less performance-hungry implementation of detail-view paging (don't return all items on a single view)
  27  * @todo Use automatic has-many and many-many functions to return a ComponentSet rather than building the join manually
  28  * @package forms
  29  * @subpackage fields-relational
  30  */
  31 class ComplexTableField extends TableListField {
  32 
  33     /**
  34      * Determines the fields of the detail pop-up form.  It can take many forms:
  35      *  - A FieldSet object: Use that field set directly.
  36      *  - A method name, eg, 'getCMSFields': Call that method on the child object to get the fields.
  37      */
  38     protected $addTitle;
  39     
  40     protected $detailFormFields;
  41     
  42     protected $viewAction, $sourceJoin, $sourceItems;
  43 
  44     /**
  45      * @var Controller
  46      */
  47     protected $controller;
  48 
  49     /**
  50      * @var string Classname of the parent-relation to correctly link new records.
  51      */
  52     public $parentClass;
  53 
  54     /**
  55      * @var string Database column name for the used relation (e.g. FamilyID
  56      * if one Family has_many Individuals).
  57      */
  58     protected $parentIdName;
  59 
  60     /**
  61      * @var array Influence output without having to subclass the template.
  62      */
  63     protected $permissions = array(
  64         "add",
  65         "edit",
  66         "show",
  67         "delete",
  68         //"export",
  69     );
  70     
  71     /**
  72      * Template for main rendering
  73      *
  74      * @var string
  75      */
  76     protected $template = "ComplexTableField";
  77 
  78     /**
  79      * Template for popup (form rendering)
  80      *
  81      * @var string
  82      */
  83     public $templatePopup = "ComplexTableField_popup";
  84 
  85     /**
  86      * Classname for each row/item
  87      *
  88      * @var string
  89      */
  90     public $itemClass = 'ComplexTableField_Item';
  91     
  92     /**
  93      * Classname for the popup form
  94      *
  95      * @var string
  96      */
  97     public $popupClass = 'ComplexTableField_Popup';
  98     
  99     /**
 100      * @var boolean Trigger pagination (defaults to true for ComplexTableField)
 101      */
 102     protected $showPagination = true;
 103 
 104     /**
 105      * @var string Caption the popup will show (defaults to the selected action).
 106      * This is set by javascript and used by greybox.
 107      */
 108     protected $popupCaption = null;
 109     
 110     /**
 111      * @var callback A function callback invoked
 112      * after initializing the popup and its base calls to
 113      * the {@link Requirements} class.
 114      */
 115     public $requirementsForPopupCallback = null;
 116 
 117     /**
 118      * @var $detailFormValidator Validator
 119      */
 120     protected $detailFormValidator = null;
 121     
 122     /**
 123      * Automatically detect a has-one relationship
 124      * in the popup (=child-class) and save the relation ID.
 125      *
 126      * @var boolean
 127      */
 128     protected $relationAutoSetting = true;
 129     
 130     /**
 131      * Default size for the popup box
 132      */
 133     protected $popupWidth = 560;
 134     protected $popupHeight = 390;
 135     
 136     public $defaultAction = 'show';
 137     
 138     public $actions = array(
 139         'show' => array(
 140             'label' => 'Show',
 141             'icon' => 'cms/images/show.png',
 142             'icon_disabled' => 'cms/images/show_disabled.png',
 143             'class' => 'popuplink showlink',
 144         ),
 145         'edit' => array(
 146             'label' => 'Edit',
 147             'icon' => 'cms/images/edit.gif', 
 148             'icon_disabled' => 'cms/images/edit_disabled.gif',
 149             'class' => 'popuplink editlink',
 150         ),
 151         'delete' => array(
 152             'label' => 'Delete',
 153             'icon' => 'cms/images/delete.gif', 
 154             'icon_disabled' => 'cms/images/delete_disabled.gif',
 155             'class' => 'popuplink deletelink',
 156         ),
 157     );
 158 
 159     static $url_handlers = array(
 160         'item/$ID' => 'handleItem',
 161         '$Action!' => '$Action',
 162     );
 163 
 164     function handleItem($request) {
 165         return new ComplexTableField_ItemRequest($this, $request->param('ID'));
 166     }
 167     
 168     function getViewer() {
 169         return new SSViewer($this->template);
 170     }
 171 
 172     function setPopupSize($width, $height) {
 173         $width = (int)$width;
 174         $height = (int)$height;
 175         
 176         if($width < 0 || $height < 0) {
 177             user_error("setPopupSize expects non-negative arguments.", E_USER_WARNING);
 178             return;
 179         }
 180         
 181         $this->popupWidth = $width;
 182         $this->popupHeight = $height;
 183     }
 184     
 185     function PopupWidth() {
 186         return $this->popupWidth;
 187     }
 188            
 189     function PopupHeight() {
 190         return $this->popupHeight;
 191     }
 192     
 193     /**
 194      * See class comments
 195      *
 196      * @param ContentController $controller
 197      * @param string $name
 198      * @param string $sourceClass
 199      * @param array $fieldList
 200      * @param FieldSet $detailFormFields
 201      * @param string $sourceFilter
 202      * @param string $sourceSort
 203      * @param string $sourceJoin
 204      */
 205     function __construct($controller, $name, $sourceClass, $fieldList = null, $detailFormFields = null, $sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
 206         $this->detailFormFields = $detailFormFields;
 207         $this->controller = $controller;
 208         $this->pageSize = 10;
 209         
 210         parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
 211     }
 212 
 213     /**
 214      * Return the record filter for this table.
 215      * It will automatically add a relation filter if relationAutoSetting is true, and it can determine an appropriate
 216      * filter.
 217      */
 218     function sourceFilter() {
 219         $sourceFilter = parent::sourceFilter();
 220         
 221         if($this->relationAutoSetting
 222                 && $this->getParentClass() 
 223                 && ($filterKey = $this->getParentIdName($this->getParentClass(), $this->sourceClass()))
 224                 && ($filterValue = $this->sourceID()) ) {
 225                     
 226             $newFilter = "\"$filterKey\" = '" . Convert::raw2sql($filterValue) . "'";
 227 
 228             if($sourceFilter && is_array($sourceFilter)) {
 229                 // Note that the brackets below are taken into account when building this
 230                 $sourceFilter = implode(") AND (", $sourceFilter);
 231             }
 232 
 233             $sourceFilter = $sourceFilter ? "($sourceFilter) AND ($newFilter)" : $newFilter;
 234         }
 235         return $sourceFilter;
 236     }
 237 
 238     function isComposite() {
 239         return false;
 240     }
 241 
 242     /**
 243      * @return String
 244      */
 245     function FieldHolder() {
 246         Requirements::javascript(THIRDPARTY_DIR . "/greybox/AmiJS.js");
 247         Requirements::javascript(THIRDPARTY_DIR . "/greybox/greybox.js");
 248         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
 249         Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
 250         Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField.js");
 251         Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
 252         Requirements::css(SAPPHIRE_DIR . "/css/TableListField.css");
 253         Requirements::css(SAPPHIRE_DIR . "/css/ComplexTableField.css");
 254         
 255         // set caption if required
 256         if($this->popupCaption) {
 257             $id = $this->id();
 258             if(Director::is_ajax()) {
 259             $js = <<<JS
 260 $('$id').GB_Caption = '$this->popupCaption';
 261 JS;
 262                 FormResponse::add($js);
 263             } else {
 264             $js = <<<JS
 265 Event.observe(window, 'load', function() { \$('$id').GB_Caption = '$this->popupCaption'; });
 266 JS;
 267                 Requirements::customScript($js);
 268             }
 269         }
 270 
 271         // compute sourceItems here instead of Items() to ensure that
 272         // pagination and filters are respected on template accessors
 273         $this->sourceItems();
 274 
 275         return $this->renderWith($this->template);
 276     }
 277 
 278     function sourceClass() {
 279         return $this->sourceClass;
 280     }
 281 
 282     /**
 283      * @return DataObjectSet
 284      */
 285     function Items() {
 286         $this->sourceItems = $this->sourceItems();
 287 
 288         if(!$this->sourceItems) {
 289             return null;
 290         }
 291 
 292         $pageStart = (isset($_REQUEST['ctf'][$this->Name()]['start']) && is_numeric($_REQUEST['ctf'][$this->Name()]['start'])) ? $_REQUEST['ctf'][$this->Name()]['start'] : 0;
 293         $this->sourceItems->setPageLimits($pageStart, $this->pageSize, $this->totalCount);
 294 
 295         $output = new DataObjectSet();
 296         foreach($this->sourceItems as $pageIndex=>$item) {
 297             $output->push(new $this->itemClass($item, $this));
 298         }
 299         return $output;
 300     }
 301 
 302     /**
 303      * Sets the popup-title by javascript. Make sure to use FormResponse in ajax-requests,
 304      * otherwise the title-change will only take effect on items existing during page-load.
 305      *
 306      * @param $caption String
 307      */
 308     function setPopupCaption($caption) {
 309         $this->popupCaption = Convert::raw2js($caption);
 310     }
 311 
 312     /**
 313      * @param $validator Validator
 314      */
 315     function setDetailFormValidator( Validator $validator ) {
 316         $this->detailFormValidator = $validator;
 317     }
 318     
 319     function setAddTitle($addTitle) {
 320         if(is_string($addTitle))
 321             $this->addTitle = $addTitle;
 322     }
 323     
 324     function Title() {
 325         return $this->addTitle ? $this->addTitle : parent::Title();
 326     }
 327 
 328     /**
 329      * Calculates the number of columns needed for colspans
 330      * used in template
 331      *
 332      * @return Int
 333      */
 334     function ItemCount() {
 335         return count($this->fieldList);
 336     }
 337 
 338     /**
 339      * Used to toggle paging (makes no sense when adding a record)
 340      *
 341      * @return Boolean
 342      */
 343     function IsAddMode() {
 344         return ($this->methodName == "add" || $this->request->param('Action') == 'AddForm');
 345     }
 346     
 347     function sourceID() { 
 348         $idField = $this->form->dataFieldByName('ID'); 
 349 
 350         // disabled as it conflicts with scaffolded formfields, and not strictly necessary
 351         // if(!$idField) user_error("ComplexTableField needs a formfield named 'ID' to be present", E_USER_ERROR); 
 352 
 353         // because action_callfieldmethod never actually loads data into the form,
 354         // we can't rely on $idField being populated, and fall back to the request-params.
 355         // this is a workaround for a bug where each subsequent popup-call didn't have ID
 356         // of the parent set, and so didn't properly save the relation
 357         return ($idField) ? $idField->Value() : (isset($_REQUEST['ctf']['ID']) ? $_REQUEST['ctf']['ID'] : null); 
 358     } 
 359      
 360 
 361 
 362     function AddLink() {
 363         return Controller::join_links($this->Link(), 'add');
 364     }
 365 
 366     /**
 367      * @return FieldSet
 368      */
 369     function createFieldSet() {
 370         $fieldset = new FieldSet();
 371         foreach($this->fieldTypes as $key => $fieldType){
 372             $fieldset->push(new $fieldType($key));
 373         }
 374         return $fieldset;
 375     }
 376 
 377     function setController($controller) {
 378         $this->controller = $controller;
 379     }
 380 
 381     /**
 382      * Determines on which relation-class the DetailForm is saved
 383      * by looking at the surrounding form-record.
 384      *
 385      * @return String
 386      */
 387     function getParentClass() {
 388         if($this->parentClass === false) {
 389             // purposely set parent-relation to false
 390             return false;
 391         } elseif(!empty($this->parentClass)) {
 392             return $this->parentClass;
 393         } elseif($this->form && $this->form->getRecord()) {
 394             return $this->form->getRecord()->ClassName;
 395         }
 396     }
 397 
 398     /**
 399      * Return the record in which the CTF resides, if it exists.
 400      */
 401     function getParentRecord() {
 402         if($this->form && $record = $this->form->getRecord()) {
 403             return $record;
 404         } else {
 405             $parentID = (int)$this->sourceID();
 406             $parentClass = $this->getParentClass();
 407             
 408             if($parentClass) {
 409                 if($parentID) return DataObject::get_by_id($parentClass, $parentID);
 410                 else return singleton($parentClass);
 411             }
 412         }
 413     }
 414 
 415     /**
 416      * (Optional) Setter for a correct parent-relation-class.
 417      * Defaults to the record loaded into the surrounding form as a fallback.
 418      * Caution: Please use the classname, not the actual column-name in the database.
 419      *
 420      * @param $className string
 421      */
 422     function setParentClass($className) {
 423         $this->parentClass = $className;
 424     }
 425 
 426     /**
 427      * Returns the db-fieldname of the currently used has_one-relationship.
 428      */
 429     function getParentIdName($parentClass, $childClass) {
 430         return $this->getParentIdNameRelation($childClass, $parentClass, 'has_one');
 431     }
 432     
 433     /**
 434      * Manually overwrites the parent-ID relations.
 435      * @see setParentClass()
 436      * 
 437      * @param String $str Example: FamilyID (when one Individual has_one Family)
 438      */
 439     function setParentIdName($str) {
 440         $this->parentIdName = $str;
 441     }
 442     
 443     /**
 444      * Returns the db-fieldname of the currently used relationship.
 445      * Note: constructed resolve ambiguous cases in the same manner as
 446      * DataObject::getComponentJoinField()
 447      */
 448     function getParentIdNameRelation($parentClass, $childClass, $relation) {
 449         if($this->parentIdName) return $this->parentIdName;
 450         
 451         $relations = array_flip(singleton($parentClass)->$relation());
 452         
 453         $classes = array_reverse(ClassInfo::ancestry($childClass));
 454         foreach($classes as $class) {
 455             if(isset($relations[$class])) return $relations[$class] . 'ID'; // !!! was comment out
 456         }
 457         return false;
 458     }
 459 
 460     function setTemplatePopup($template) {
 461         $this->templatePopup = $template;
 462     }
 463 
 464     ////////////////////////////////////////////////////////////////////////////////////////////////////
 465 
 466     /**
 467      * Return the object-specific fields for the given record, to be shown in the detail pop-up
 468      * 
 469      * This won't include all the CTF-specific 'plumbing; this method is called by self::getFieldsFor()
 470      * and the result is then processed further to get the actual FieldSet for the form.
 471      *
 472      * The default implementation of this processes the value of $this->detailFormFields; consequently, if you want to 
 473      * set the value of the fields to something that $this->detailFormFields doesn't allow, you can do so by overloading
 474      * this method.
 475      */
 476     function getCustomFieldsFor($childData) {
 477         if($this->detailFormFields instanceof FieldSet) {
 478             return $this->detailFormFields;
 479         }
 480         
 481         $fieldsMethod = $this->detailFormFields;
 482 
 483         if(!is_string($fieldsMethod)) {
 484             $this->detailFormFields = 'getCMSFields';
 485             $fieldsMethod = 'getCMSFields';
 486         }
 487         
 488         if(!$childData->hasMethod($fieldsMethod)) {
 489             $fieldsMethod = 'getCMSFields';
 490         }
 491         
 492         return $childData->$fieldsMethod();
 493     }
 494         
 495     function getFieldsFor($childData) {
 496         $hasManyRelationName = null;
 497         $manyManyRelationName = null;
 498     
 499         // See if our parent class has any many_many relations by this source class
 500         if($parentClass = $this->getParentRecord()) {
 501             $manyManyRelations = $parentClass->many_many();
 502             $manyManyRelationName = null;
 503             $manyManyComponentSet = null;
 504 
 505             $hasManyRelations = $parentClass->has_many();
 506             $hasManyRelationName = null;
 507             $hasManyComponentSet = null;
 508 
 509             if($manyManyRelations) foreach($manyManyRelations as $relation => $class) {
 510                 if($class == $this->sourceClass()) {
 511                     $manyManyRelationName = $relation;
 512                 }
 513             }
 514 
 515             if($hasManyRelations) foreach($hasManyRelations as $relation => $class) {
 516                 if($class == $this->sourceClass()) {
 517                     $hasManyRelationName = $relation;
 518                 }
 519             }
 520         }
 521         
 522         // Add the relation value to related records
 523         if(!$childData->ID && $this->getParentClass()) {
 524             // make sure the relation-link is existing, even if we just add the sourceClass and didn't save it
 525             $parentIDName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
 526             $childData->$parentIDName = $this->sourceID();
 527         }
 528         
 529         $detailFields = $this->getCustomFieldsFor($childData);
 530 
 531         if($this->getParentClass() && $hasManyRelationName && $childData->ID) {
 532             $hasManyComponentSet = $parentClass->getComponents($hasManyRelationName);
 533         }
 534 
 535         // the ID field confuses the Controller-logic in finding the right view for ReferencedField
 536         $detailFields->removeByName('ID');
 537         
 538         // only add childID if we're not adding a record        
 539         if($childData->ID) {
 540             $detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID));
 541         }
 542         
 543         // add a namespaced ID instead thats "converted" by saveComplexTableField()
 544         $detailFields->push(new HiddenField('ctf[ClassName]', '', $this->sourceClass()));
 545 
 546         if($this->getParentClass()) {
 547             $detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
 548 
 549             if($manyManyRelationName && $this->relationAutoSetting) {
 550                 $detailFields->push(new HiddenField('ctf[manyManyRelation]', '', $manyManyRelationName));
 551             }
 552             
 553             if($hasManyRelationName && $this->relationAutoSetting) {
 554                 $detailFields->push(new HiddenField('ctf[hasManyRelation]', '', $hasManyRelationName));
 555             }
 556             
 557             if($manyManyRelationName || $hasManyRelationName) {
 558                 $detailFields->push(new HiddenField('ctf[sourceID]', '', $this->sourceID()));
 559             }
 560             
 561             $parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
 562             
 563             if($parentIdName) {
 564                 if($this->relationAutoSetting) {
 565                     // Hack for model admin: model admin will have included a dropdown for the relation itself
 566                     $detailFields->removeByName($parentIdName);
 567                     $detailFields->push(new HiddenField($parentIdName, '', $this->sourceID()));
 568                 }
 569             }
 570         } 
 571         
 572         return $detailFields;
 573     }
 574 
 575     function getValidatorFor($childData) {
 576         // if no custom validator is set, and there's on present on the object (e.g. Member), use it
 577         if(!isset($this->detailFormValidator) && $childData->hasMethod('getValidator')) {
 578             $this->detailFormValidator = $childData->getValidator();
 579         }
 580         return $this->detailFormValidator;
 581     }
 582     
 583     ////////////////////////////////////////////////////////////////////////////////////////////////////
 584     
 585     function add() {
 586         if(!$this->can('add')) return;
 587         
 588         return $this->customise(array(
 589             'DetailForm' => $this->AddForm(),
 590         ))->renderWith($this->templatePopup);
 591     }
 592 
 593     function AddForm($childID = null) {
 594         $className = $this->sourceClass();
 595         $childData = new $className();
 596         
 597         $fields = $this->getFieldsFor($childData);
 598         $validator = $this->getValidatorFor($childData);
 599 
 600         $form = new $this->popupClass(
 601             $this,
 602             'AddForm',
 603             $fields,
 604             $validator,
 605             false,
 606             $childData
 607         );
 608 
 609         $form->loadDataFrom($childData);
 610 
 611         return $form;
 612     }
 613     
 614     /**
 615      * By default, a ComplexTableField will assume that the field name is the name of a has-many relation on the object being
 616      * edited.  It will identify the foreign key in the object being listed, and filter on that column, as well as auto-setting
 617      * that column for newly created records.
 618      * 
 619      * Calling $this->setRelationAutoSetting(false) will disable this functionality.
 620      *
 621      * @param boolean $value Should the relation auto-setting functionality be enabled?
 622      */
 623     function setRelationAutoSetting($value) {
 624         $this->relationAutoSetting = $value;
 625     }
 626     
 627     /**
 628      * Use the URL-Parameter "action_saveComplexTableField"
 629      * to provide a clue to the main controller if the main form has to be rendered,
 630      * even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
 631      * which in turn saves the record.
 632      *
 633      * This is for adding new item records. {@link ComplexTableField_ItemRequest::saveComplexTableField()}
 634      *
 635      * @see Form::ReferencedField
 636      */
 637     function saveComplexTableField($data, $form, $params) {
 638         $className = $this->sourceClass();
 639         $childData = new $className();
 640         $form->saveInto($childData);
 641 
 642         try {
 643             $childData->write();
 644         } catch(ValidationException $e) {
 645             $form->sessionMessage($e->getResult()->message(), 'bad');
 646             return Director::redirectBack();
 647         }
 648 
 649         // Save the many many relationship if it's available
 650         if(isset($data['ctf']['manyManyRelation'])) {
 651             $parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
 652             $relationName = $data['ctf']['manyManyRelation'];
 653             $componentSet = $parentRecord->getManyManyComponents($relationName);
 654             $componentSet->add($childData);
 655         }
 656         
 657         if(isset($data['ctf']['hasManyRelation'])) {
 658             $parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
 659             $relationName = $data['ctf']['hasManyRelation'];
 660             
 661             $componentSet = $parentRecord->getComponents($relationName);
 662             $componentSet->add($childData);
 663         }
 664         
 665         $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
 666         
 667         $closeLink = sprintf(
 668             '<small><a href="%s" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
 669             $referrer,
 670             _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
 671         );
 672         
 673         $editLink = Controller::join_links($this->Link(), 'item/' . $childData->ID . '/edit');
 674         
 675         $message = sprintf(
 676             _t('ComplexTableField.SUCCESSADD', 'Added %s %s %s'),
 677             $childData->i18n_singular_name(),
 678             '<a href="' . $editLink . '">' . $childData->Title . '</a>',
 679             $closeLink
 680         );
 681         
 682         $form->sessionMessage($message, 'good');
 683 
 684         Director::redirectBack();
 685     }
 686 }
 687 
 688 /**
 689  * @todo Tie this into ComplexTableField_Item better.
 690  * @package forms
 691  * @subpackage fields-relational
 692  */
 693 class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
 694     protected $ctf;
 695     protected $itemID;
 696     protected $methodName;
 697     
 698     static $url_handlers = array(
 699         '$Action!' => '$Action',
 700         '' => 'index',
 701     );
 702     
 703     function Link($action = null) {
 704         return Controller::join_links($this->ctf->Link(), '/item/', $this->itemID, $action);
 705     }
 706         
 707     function index() {
 708         return $this->show();
 709     }
 710 
 711     /**
 712      * Just a hook, processed in {DetailForm()}
 713      *
 714      * @return String
 715      */
 716     function show() {
 717         if($this->ctf->Can('show') !== true) {
 718             return false;
 719         }
 720 
 721         $this->methodName = "show";
 722         return $this->renderWith($this->ctf->templatePopup);
 723     }
 724     
 725     /**
 726      * Returns a 1-element data object set that can be used for pagination.
 727      */
 728     /* this doesn't actually work :-(
 729     function Paginator() { 
 730         $paginatingSet = new DataObjectSet(array($this->dataObj()));
 731         $start = isset($_REQUEST['ctf']['start']) ? $_REQUEST['ctf']['start'] : 0;
 732         $paginatingSet->setPageLimits($start, 1, $this->ctf->TotalCount());
 733         return $paginatingSet;
 734     }
 735     */
 736 
 737     /**
 738      * Just a hook, processed in {DetailForm()}
 739      *
 740      * @return String
 741      */
 742     function edit() {
 743         if($this->ctf->Can('edit') !== true) {
 744             return false;
 745         }
 746 
 747         $this->methodName = "edit";
 748 
 749         return $this->renderWith($this->ctf->templatePopup);
 750     }
 751 
 752     function delete() {
 753         if($this->ctf->Can('delete') !== true) {
 754             return false;
 755         }
 756 
 757         $this->dataObj()->delete();
 758     }
 759 
 760     ///////////////////////////////////////////////////////////////////////////////////////////////////
 761 
 762     /**
 763      * Return the data object being manipulated
 764      */
 765     function dataObj() {
 766         // used to discover fields if requested and for population of field
 767         if(is_numeric($this->itemID)) {
 768             // we have to use the basedataclass, otherwise we might exclude other subclasses 
 769             return DataObject::get_by_id(ClassInfo::baseDataClass(Object::getCustomClass($this->ctf->sourceClass())), $this->itemID); 
 770         }
 771         
 772     }
 773 
 774     /**
 775      * Renders view, edit and add, depending on the given information.
 776      * The form needs several parameters to function independently of its "parent-form", some derived from the context into a hidden-field,
 777      * some derived from the parent context (which is not accessible here) and delivered by GET:
 778      * ID, Identifier of the currently edited record (only if record is loaded).
 779      * <parentIDName>, Link back to the correct parent record (e.g. "parentID").
 780      * parentClass, Link back to correct container-class (the parent-record might have many 'has-one'-relationships)
 781      * CAUTION: "ID" in the DetailForm would be the "childID" in the overview table.
 782      * 
 783      * @param int $childID
 784      */
 785     function DetailForm($childID = null) {
 786         $childData = $this->dataObj();
 787 
 788         $fields = $this->ctf->getFieldsFor($childData);
 789         $validator = $this->ctf->getValidatorFor($childData);
 790         $readonly = ($this->methodName == "show");
 791 
 792         $form = new $this->ctf->popupClass(
 793             $this,
 794             "DetailForm", 
 795             $fields,
 796             $validator,
 797             $readonly,
 798             $childData
 799         );
 800     
 801         $form->loadDataFrom($childData);
 802         if ($readonly) $form->makeReadonly();
 803 
 804         return $form;
 805     }
 806 
 807     /**
 808      * Use the URL-Parameter "action_saveComplexTableField"
 809      * to provide a clue to the main controller if the main form has to be rendered,
 810      * even if there is no action relevant for the main controller (to provide the instance of ComplexTableField
 811      * which in turn saves the record.
 812      *
 813      * This is for editing existing item records. {@link ComplexTableField::saveComplexTableField()}
 814      *
 815      * @see Form::ReferencedField
 816      */
 817     function saveComplexTableField($data, $form, $request) {
 818         $dataObject = $this->dataObj();
 819 
 820         try {
 821             $form->saveInto($dataObject);
 822             $dataObject->write();
 823         } catch(ValidationException $e) {
 824             $form->sessionMessage($e->getResult()->message(), 'bad');
 825             return Director::redirectBack();
 826         }
 827         
 828         // Save the many many relationship if it's available
 829         if(isset($data['ctf']['manyManyRelation'])) {
 830             $parentRecord = DataObject::get_by_id($data['ctf']['parentClass'], (int) $data['ctf']['sourceID']);
 831             $relationName = $data['ctf']['manyManyRelation'];
 832             $componentSet = $parentRecord->getManyManyComponents($relationName);
 833             $componentSet->add($dataObject);
 834         }
 835         
 836         $referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
 837         
 838         $closeLink = sprintf(
 839             '<small><a href="%s" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
 840             $referrer,
 841             _t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
 842         );
 843         $message = sprintf(
 844             _t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'),
 845             $dataObject->i18n_singular_name(),
 846             '<a href="' . $this->Link() . '">"' . htmlspecialchars($dataObject->Title, ENT_QUOTES) . '"</a>',
 847             $closeLink
 848         );
 849         
 850         $form->sessionMessage($message, 'good');
 851 
 852         Director::redirectBack();
 853     }
 854     
 855     function PopupCurrentItem() {
 856         return $_REQUEST['ctf']['start']+1;
 857     }
 858     
 859     function PopupFirstLink() {
 860         $this->ctf->LinkToItem();
 861         
 862         if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
 863             return null;
 864         }
 865 
 866         $start = 0;
 867         return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
 868     }
 869 
 870     function PopupLastLink() {
 871         if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
 872             return null;
 873         }
 874         
 875         $start = $this->totalCount - 1;
 876         return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
 877     }
 878 
 879     function PopupNextLink() {
 880         if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == $this->totalCount-1) {
 881             return null;
 882         }
 883 
 884         $start = $_REQUEST['ctf']['start'] + 1;
 885         return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
 886     }
 887 
 888     function PopupPrevLink() {
 889         if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start']) || $_REQUEST['ctf']['start'] == 0) {
 890             return null;
 891         }
 892 
 893         $start = $_REQUEST['ctf']['start'] - 1;
 894         return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
 895     }
 896     
 897     /**
 898      * Method handles pagination in asset popup.
 899      *
 900      * @return Object DataObjectSet
 901      */
 902     
 903     function Pagination() {
 904         $this->pageSize = 9;
 905         $currentItem  = $this->PopupCurrentItem();
 906         $result = new DataObjectSet();
 907         if($currentItem < 6) {
 908             $offset = 1;
 909         } elseif($this->totalCount - $currentItem <= 4) {
 910             $offset = $currentItem - (10 - ($this->totalCount - $currentItem));
 911             $offset = $offset <= 0 ? 1 : $offset;
 912         } else {
 913             $offset = $currentItem  - 5; 
 914         }
 915         for($i = $offset;$i <= $offset + $this->pageSize && $i <= $this->totalCount;$i++) {
 916             $start = $i - 1;
 917             $links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}");
 918             $links['number'] = $i;
 919             $links['active'] = $i == $currentItem ? false : true;
 920             $result->push(new ArrayData($links));   
 921         }
 922         return $result;
 923     }
 924 
 925     function ShowPagination() {
 926         return false;
 927     }
 928 
 929 
 930     /**
 931      * #################################
 932      *           Utility
 933      * #################################
 934      */
 935 
 936     /**
 937      * Get part of class ancestry (even if popup is not subclassed it might be styled differently in css)
 938      */
 939     function PopupClasses() {
 940         global $_ALL_CLASSES;
 941 
 942         $items = array();
 943         $parents = isset($_ALL_CLASSES['parents'][$this->class]) ? $_ALL_CLASSES['parents'][$this->class] : null;
 944         
 945         if($parents) {
 946             foreach($parents as $parent) {
 947                 if(!in_array($parent, $_ALL_CLASSES['parents']['TableListField'])) {
 948                     $items[] = $parent . '_Popup';
 949                 }
 950             }
 951         }
 952         
 953         $items[] = $this->class . '_Popup';
 954 
 955         return implode(' ', $items);
 956     }
 957 
 958 
 959     /**
 960      * Returns the db-fieldname of the currently used has_one-relationship.
 961      */
 962     function getParentIdName($parentClass, $childClass) {
 963         return $this->getParentIdNameRelation($childClass, $parentClass, 'has_one');
 964     }
 965     
 966     /**
 967      * Manually overwrites the parent-ID relations.
 968      * @see setParentClass()
 969      * 
 970      * @param String $str Example: FamilyID (when one Individual has_one Family)
 971      */
 972     function setParentIdName($str) {
 973         $this->parentIdName = $str;
 974     }
 975     
 976     /**
 977      * Returns the db-fieldname of the currently used relationship.
 978      */
 979     function getParentIdNameRelation($parentClass, $childClass, $relation) {
 980         if($this->parentIdName) return $this->parentIdName; 
 981         
 982         $relations = singleton($parentClass)->$relation();
 983         $classes = ClassInfo::ancestry($childClass);
 984         if($relations) {
 985             foreach($relations as $k => $v) {
 986                 if(array_key_exists($v, $classes)) return $k . 'ID';
 987             }
 988         }
 989         return false;
 990     }
 991 
 992     function setTemplatePopup($template) {
 993         $this->templatePopup = $template;
 994     }
 995 
 996 
 997 }
 998 
 999 /**
1000  * Single row of a {@link ComplexTableField}.
1001  * @package forms
1002  * @subpackage fields-relational
1003  */
1004 class ComplexTableField_Item extends TableListField_Item {
1005     function Link($action = null) {
1006         return Controller::join_links($this->parent->Link(), '/item/', $this->item->ID, $action);
1007     }
1008 
1009     function EditLink() {
1010         return Controller::join_links($this->Link(), "edit");
1011     }
1012 
1013     function ShowLink() {
1014         return Controller::join_links($this->Link(), "show");
1015     }
1016 
1017     function DeleteLink() {
1018         return Controller::join_links($this->Link(), "delete");
1019     }
1020     
1021     /**
1022      * @param String $action
1023      * @return boolean
1024      */
1025     function IsDefaultAction($action) {
1026         return ($action == $this->parent->defaultAction);
1027     }
1028 }
1029 
1030 
1031 /**
1032  * ComplexTablefield_popup is rendered with a lightbox and can load a more
1033  * detailed view of the source class your presenting.
1034  * You can customise the fields and requirements as well as any
1035  * permissions you might need.
1036  * @package forms
1037  * @subpackage fields-relational
1038  */
1039 class ComplexTableField_Popup extends Form {
1040     protected $sourceClass;
1041     
1042     protected $dataObject;
1043 
1044     function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
1045         $this->dataObject = $dataObject;
1046         
1047         Requirements::clear();
1048         Requirements::unblock_all();
1049         
1050         $actions = new FieldSet();  
1051         if(!$readonly) {
1052             $actions->push(
1053                 $saveAction = new FormAction(
1054                     "saveComplexTableField", 
1055                     _t('CMSMain.SAVE')
1056                 )
1057             );  
1058             $saveAction->addExtraClass('save');
1059         }
1060         
1061         parent::__construct($controller, $name, $fields, $actions, $validator);
1062     }
1063 
1064     function forTemplate() {
1065         $ret = parent::forTemplate();
1066         
1067         /**
1068          * WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
1069          * Some have special requirements.
1070          */
1071         Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
1072         Requirements::css(SAPPHIRE_DIR . '/css/ComplexTableField_popup.css');
1073         Requirements::css(CMS_DIR . '/css/typography.css');
1074         Requirements::css(CMS_DIR . '/css/cms_right.css');
1075         Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
1076         Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
1077         Requirements::javascript(SAPPHIRE_DIR . "/javascript/prototype_improvements.js");
1078         Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
1079         Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
1080         Requirements::javascript(SAPPHIRE_DIR . "/javascript/layout_helpers.js");
1081         Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
1082         Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField_popup.js");
1083 
1084         // Append requirements from instance callbacks
1085         $parent = $this->getParentController();
1086         if($parent instanceof ComplexTableField) {
1087             $callback = $parent->requirementsForPopupCallback;
1088         } else {
1089             $callback = $parent->getParentController()->requirementsForPopupCallback;
1090         }
1091         if($callback) call_user_func($callback, $this);
1092 
1093         // Append requirements from DataObject
1094         // DEPRECATED 2.4 Use ComplexTableField->requirementsForPopupCallback
1095         if($this->dataObject && $this->dataObject->hasMethod('getRequirementsForPopup')) {
1096             $this->dataObject->getRequirementsForPopup();
1097         }
1098         
1099         return $ret;
1100     }
1101     
1102     /**
1103      * @return ComplexTableField_ItemRequest
1104      */
1105     function getParentController() {
1106         return $this->controller;
1107     }
1108 }
1109 
1110 ?>
1111 
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -
Webylon 3.1 API Docs API documentation generated by ApiGen 2.8.0