mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Removing deprecated TableListField and subclasses
These have been moved to a module called "legacytablefields" located at https://github.com/silverstripe-labs/legacytablefields
This commit is contained in:
parent
aeef4d6e84
commit
77337ae58c
@ -1,6 +0,0 @@
|
||||
/* table */
|
||||
.ComplexTableField { margin-bottom: 10px; }
|
||||
.ComplexTableField tbody td { cursor: pointer; }
|
||||
.ComplexTableField tbody td.markingcheckbox { cursor: default; }
|
||||
|
||||
.ui-dialog .ctf-dialog.ui-dialog-content { padding-right: 0; /* scrollbars */ }
|
@ -1,30 +0,0 @@
|
||||
#right form .hasmanyfile a.addFile, #right form .hasmanyfile a.uploadFile, #right form .hasmanyfile .currentFiles li { font-size: 1.2em; padding-left: 3px; }
|
||||
|
||||
#right form .hasmanyfile a.uploadFile { border-color: #cccccc #999999 #999999 #cccccc; border-style: solid; border-width: 2px; color: #333333; cursor: pointer; font-size: 11px; font-weight: bold; position: relative; top: -29px; left: 344px; text-decoration: none; overflow: visible; padding: 3px 5px; float: left; width: auto; }
|
||||
|
||||
#right form .hasmanyfile a.removeFile { border-color: #cccccc #999999 #999999 #cccccc; border-style: solid; border-width: 2px; color: #333333; cursor: pointer; font-size: 10px; font-weight: bold; text-decoration: none; overflow: visible; padding: 3px 5px; width: auto; margin: -2px 0 0 10px; }
|
||||
|
||||
#right form .hasmanyfile a.removeFile:hover { background: #CE0000; color: #fff; }
|
||||
|
||||
#right form .hasmanyfile a.addFile { border-color: #cccccc #999999 #999999 #cccccc; border-style: solid; border-width: 2px; color: #333333; cursor: pointer; font-size: 11px; font-weight: bold; position: relative; top: -29px; left: 340px; text-decoration: none; overflow: visible; padding: 3px 5px; float: left; width: auto; }
|
||||
|
||||
#right form .hasmanyfile a.addFile:hover, #right form .hasmanyfile a.uploadFile:hover { background: #fff; }
|
||||
|
||||
#right form .hasmanyfile ul.currentFiles { padding-bottom: 5px; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles li { height: 30px; line-height: 30px; }
|
||||
|
||||
#right form .hasmanyfile .clear { clear: both; }
|
||||
|
||||
/* ICONS */
|
||||
#right form .hasmanyfile .currentFiles a[href$=".pdf"], #right form .hasmanyfile .currentFiles a[href$=".PDF"], #right form .hasmanyfile .currentFiles a.pdf { padding: 2px; padding-left: 20px; background: url(../images/icons/page_white_acrobat.png) no-repeat left center; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles a[href$=".doc"], #right form .hasmanyfile .currentFiles a[href$=".DOC"], #right form .hasmanyfile .currentFiles a.doc { padding: 2px; padding-left: 20px; background: url(../images/icons/page_word.png) no-repeat left center; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles a[href$=".xls"], #right form .hasmanyfile .currentFiles a[href$=".XLS"], #right form .hasmanyfile .currentFiles a.xls { padding: 2px; padding-left: 20px; background: url(../images/icons/page_excel.png) no-repeat left center; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles a[href$=".gz"], #right form .hasmanyfile .currentFiles a[href$=".GZ"], #right form .hasmanyfile .currentFiles a[href$=".gzip"], #right form .hasmanyfile .currentFiles a[href$=".GZIP"], #right form .hasmanyfile .currentFiles a[href$=".zip"], #right form .hasmanyfile .currentFiles a[href$=".ZIP"], #right form .hasmanyfile .currentFiles a.archive { padding: 2px; padding-left: 20px; background: url(../images/icons/page_white_zip.png) no-repeat left center; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles a[href$=".jpg"], #right form .hasmanyfile .currentFiles a[href$=".JPG"], #right form .hasmanyfile .currentFiles a[href$=".gif"], #right form .hasmanyfile .currentFiles a[href$=".GIF"], #right form .hasmanyfile .currentFiles a[href$=".png"], #right form .hasmanyfile .currentFiles a[href$=".PNG"], #right form .hasmanyfile .currentFiles a.image { padding: 2px; padding-left: 20px; background: url(../images/icons/icon-jpg.gif) no-repeat left center; }
|
||||
|
||||
#right form .hasmanyfile .currentFiles a[href$=".exe"], #right form .hasmanyfile .currentFiles a[href$=".EXE"], #right form .hasmanyfile .currentFiles a.application { padding: 2px; padding-left: 20px; background: url(../images/icons/application.png) no-repeat left center; }
|
@ -1,76 +0,0 @@
|
||||
table.TableField, table.TableListField, .TableListField table.data, table.CMSList { border-collapse: collapse; border-spacing: 0; width: 100%; }
|
||||
|
||||
/* Preventing IE6 from showing double borders */
|
||||
body > div table.TableField, body > div table.TableListField, body > div .TableListField table.data, body > div table.CMSList { border-collapse: separate; }
|
||||
|
||||
table.TableField td, table.TableListField td, .TableListField table.data td, table.CMSList td { border-style: none; }
|
||||
|
||||
table.TableField th, table.TableListField th, .TableListField table.data th, table.CMSList th { white-space: nowrap; }
|
||||
|
||||
table.TableField thead th, .TableListField table.data thead th, table.CMSList thead th { white-space: nowrap; padding: 3px; font-size: 12px; text-align: left; }
|
||||
|
||||
table.TableField thead th span, .TableListField table.data thead th span { font-size: 12px; }
|
||||
|
||||
table.TableField thead th span.sortLink, .TableListField table.data thead th span.sortLink, table.CMSList thead th span.sortLink { overflow: hidden; }
|
||||
|
||||
table.TableField tbody td, table.TableField tfoot td, .TableListField table.data tbody td, .TableListField table.data tfoot td, table.CMSList tbody td, table.CMSList tfoot td { padding: 2px 4px; }
|
||||
|
||||
.TableListField table.data tfoot tr.addtogrouprow td { padding: 3px; }
|
||||
|
||||
.TableListField table.data tfoot .actions { float: none; }
|
||||
|
||||
.TableListField table.data tfoot tr.addtogrouprow input { width: 94%; }
|
||||
|
||||
.TableField td input, .TableListField td input { width: 98%; }
|
||||
|
||||
table.data tbody td input, table.data tbody td textarea { border: 0 !important; }
|
||||
|
||||
table.TableField tbody td.checkbox, .TableListField table.data tbody td.checkbox, table.CMSList tbody td.checkbox { padding-left: 5px; background-image: url(../images/checkbox.png); background-repeat: repeat-x; background-position: left bottom; }
|
||||
|
||||
.TableListField table.data tfoot .addlink img { vertical-align: middle; margin: 3px 6px 3px 3px; }
|
||||
|
||||
.TableListField table.data tfoot tr td a { text-decoration: none; }
|
||||
|
||||
.TableListField table.data tbody tr td a:hover, .TableListField table.data tfoot tr td a:hover { background: none; }
|
||||
|
||||
/** Show a loading indication on a TableListField row */
|
||||
.TableListField tr.loading td.first { padding-left: 22px; background: url(../images/network-save.gif) 3px 2px no-repeat; }
|
||||
|
||||
.right form .TableField span.readonly { border: 0; background: none; padding: 0; margin-bottom: 0; }
|
||||
|
||||
.right form .TableListField td { background: #fff; }
|
||||
|
||||
.TableListField div.utility { overflow: auto; }
|
||||
|
||||
.TableListField div.utility .item { margin-top: 1em; padding: 3px 0 6px 0; display: block; float: left; }
|
||||
|
||||
.TableListField div.utility a { text-decoration: none; color: #333; cursor: pointer; font-size: 11px; margin-right: 2px; overflow: visible; padding: 3px 2px; width: auto; }
|
||||
|
||||
form .TableField .message { width: auto; }
|
||||
|
||||
.TableListField .selectOptions { overflow: auto; font: 1.3em; margin: 0; padding: 0; }
|
||||
|
||||
.TableListField .selectOptions li { float: left; margin: 0px 5px; }
|
||||
|
||||
.TableListField .PageControls { margin: 5px 0; text-align: center; display: block; margin-bottom: 5px; position: relative; }
|
||||
|
||||
.TableListField .PageControls * { display: inline; vertical-align: middle; font-weight: bold; }
|
||||
|
||||
.TableListField .PageControls .Last { display: block; width: 40px; text-align: right; position: absolute; right: 0px; top: 0px; }
|
||||
|
||||
.TableListField .PageControls .First { float: left; display: block; width: 40px; text-align: left; }
|
||||
|
||||
#Pagination { margin-top: 10px; margin-left: auto; margin-right: auto; }
|
||||
#Pagination a { font-size: 14px; width: 1px; height: 1px; margin: 1px; }
|
||||
#Pagination span { display: inline; font-size: 14px; }
|
||||
#Pagination div { display: inline; }
|
||||
|
||||
#Pagination_Next a { text-decoration: none; }
|
||||
|
||||
#Pagination_Next a div { position: relative; left: -20px; }
|
||||
|
||||
#Pagination_Next a img { position: relative; top: -15px; left: 5px; }
|
||||
|
||||
#Pagination_Previous a { text-decoration: none; }
|
||||
|
||||
#Pagination_Previous a img { position: relative; top: -15px; left: 35px; }
|
@ -1,883 +0,0 @@
|
||||
<?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.
|
||||
*
|
||||
* 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,
|
||||
* to provide a "starting point" for action_callfieldmethod and ReferencedField.
|
||||
*
|
||||
* All URL data sent to and from ComplexTableField is encapsulated in $_REQUEST['ctf']
|
||||
* to avoid side-effects with the main controller.
|
||||
*
|
||||
* 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")
|
||||
* - childID Identifier of the database-record (the targeted table is determined by the $sourceClass
|
||||
* parameter)
|
||||
*
|
||||
* @deprecated 3.1 Use GridField with GridFieldConfig_RecordEditor
|
||||
*
|
||||
* @todo Control width/height of popup by constructor (hardcoded at the moment)
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ComplexTableField extends TableListField {
|
||||
|
||||
/**
|
||||
* Determines the fields of the detail pop-up form. It can take many forms:
|
||||
* - A FieldList object: Use that field set directly.
|
||||
* - A method name, eg, 'getCMSFields': Call that method on the child object to get the fields.
|
||||
*/
|
||||
protected $addTitle;
|
||||
|
||||
protected $detailFormFields;
|
||||
|
||||
protected $viewAction;
|
||||
|
||||
/**
|
||||
* @var Controller
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var string Classname of the parent-relation to correctly link new records.
|
||||
*/
|
||||
public $parentClass;
|
||||
|
||||
/**
|
||||
* @var string Database column name for the used relation (e.g. FamilyID
|
||||
* if one Family has_many Individuals).
|
||||
*/
|
||||
protected $parentIdName;
|
||||
|
||||
/**
|
||||
* @var array Influence output without having to subclass the template.
|
||||
*/
|
||||
protected $permissions = array(
|
||||
"add",
|
||||
"edit",
|
||||
"show",
|
||||
"delete",
|
||||
//"export",
|
||||
);
|
||||
|
||||
/**
|
||||
* Template for main rendering
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template = "ComplexTableField";
|
||||
|
||||
/**
|
||||
* Template for popup (form rendering)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $templatePopup = "ComplexTableField_popup";
|
||||
|
||||
/**
|
||||
* Classname for each row/item
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $itemClass = 'ComplexTableField_Item';
|
||||
|
||||
/**
|
||||
* Classname for the popup form
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $popupClass = 'ComplexTableField_Popup';
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* @var callback A function callback invoked
|
||||
* after initializing the popup and its base calls to
|
||||
* the {@link Requirements} class.
|
||||
*/
|
||||
public $requirementsForPopupCallback = null;
|
||||
|
||||
/**
|
||||
* @var $detailFormValidator Validator
|
||||
*/
|
||||
protected $detailFormValidator = null;
|
||||
|
||||
/**
|
||||
* Default size for the popup box
|
||||
*/
|
||||
protected $popupWidth = 560;
|
||||
protected $popupHeight = 390;
|
||||
|
||||
public $defaultAction = 'show';
|
||||
|
||||
public $actions = array(
|
||||
'show' => array(
|
||||
'label' => 'Show',
|
||||
'icon' => 'framework/images/show.png',
|
||||
'icon_disabled' => 'framework/images/show_disabled.png',
|
||||
'class' => 'popuplink showlink',
|
||||
),
|
||||
'edit' => array(
|
||||
'label' => 'Edit',
|
||||
'icon' => 'framework/images/edit.gif',
|
||||
'icon_disabled' => 'framework/images/edit_disabled.gif',
|
||||
'class' => 'popuplink editlink',
|
||||
),
|
||||
'delete' => array(
|
||||
'label' => 'Delete',
|
||||
'icon' => 'framework/images/delete.gif',
|
||||
'icon_disabled' => 'framework/images/delete_disabled.gif',
|
||||
'class' => 'popuplink deletelink',
|
||||
),
|
||||
);
|
||||
|
||||
static $url_handlers = array(
|
||||
'item/$ID' => 'handleItem',
|
||||
'$Action!' => '$Action',
|
||||
);
|
||||
|
||||
public function handleItem($request) {
|
||||
return new ComplexTableField_ItemRequest($this, $request->param('ID'));
|
||||
}
|
||||
|
||||
public function getViewer() {
|
||||
return new SSViewer($this->template);
|
||||
}
|
||||
|
||||
public function setPopupSize($width, $height) {
|
||||
$width = (int)$width;
|
||||
$height = (int)$height;
|
||||
|
||||
if($width < 0 || $height < 0) {
|
||||
user_error("setPopupSize expects non-negative arguments.", E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->popupWidth = $width;
|
||||
$this->popupHeight = $height;
|
||||
}
|
||||
|
||||
public function PopupWidth() {
|
||||
return $this->popupWidth;
|
||||
}
|
||||
|
||||
public function PopupHeight() {
|
||||
return $this->popupHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* See class comments
|
||||
*
|
||||
* @param Controller $controller
|
||||
* @param string $name
|
||||
* @param string $sourceClass
|
||||
* @param array $fieldList
|
||||
* @param FieldList $detailFormFields
|
||||
* @param string $sourceFilter
|
||||
* @param string $sourceSort
|
||||
* @param string $sourceJoin
|
||||
*/
|
||||
public function __construct($controller, $name, $sourceClass, $fieldList = null, $detailFormFields = null,
|
||||
$sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
|
||||
|
||||
$this->detailFormFields = $detailFormFields;
|
||||
$this->controller = $controller;
|
||||
$this->pageSize = 10;
|
||||
|
||||
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
|
||||
}
|
||||
|
||||
public function isComposite() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
public function FieldHolder($properties = array()) {
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/prototype/prototype.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/behaviour/behaviour.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/greybox/AmiJS.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/greybox/greybox.js");
|
||||
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/TableListField.js');
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/javascript/ComplexTableField.js");
|
||||
Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
|
||||
Requirements::css(FRAMEWORK_DIR . "/css/TableListField.css");
|
||||
Requirements::css(FRAMEWORK_DIR . "/css/ComplexTableField.css");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// compute sourceItems here instead of Items() to ensure that
|
||||
// pagination and filters are respected on template accessors
|
||||
$this->sourceItems();
|
||||
|
||||
return $this->renderWith($this->template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Items() {
|
||||
$sourceItems = $this->sourceItems();
|
||||
|
||||
if(!$sourceItems) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
$pageStart = $_REQUEST['ctf'][$this->getName()]['start'];
|
||||
if(!is_numeric($pageStart)) $pageStart = 0;
|
||||
} else {
|
||||
$pageStart = 0;
|
||||
}
|
||||
|
||||
$output = new ArrayList();
|
||||
foreach($sourceItems as $pageIndex=>$item) {
|
||||
$output->push(Object::create($this->itemClass,$item, $this, $pageStart+$pageIndex));
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param $caption String
|
||||
*/
|
||||
public function setPopupCaption($caption) {
|
||||
$this->popupCaption = Convert::raw2js($caption);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $validator Validator
|
||||
*/
|
||||
public function setDetailFormValidator( Validator $validator ) {
|
||||
$this->detailFormValidator = $validator;
|
||||
}
|
||||
|
||||
public function setAddTitle($addTitle) {
|
||||
if(is_string($addTitle))
|
||||
$this->addTitle = $addTitle;
|
||||
}
|
||||
|
||||
public function Title() {
|
||||
return $this->addTitle ? $this->addTitle : parent::Title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of columns needed for colspans
|
||||
* used in template
|
||||
*
|
||||
* @return Int
|
||||
*/
|
||||
public function ItemCount() {
|
||||
return count($this->fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to toggle paging (makes no sense when adding a record)
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public function IsAddMode() {
|
||||
return ($this->methodName == "add" || $this->request->param('Action') == 'AddForm');
|
||||
}
|
||||
|
||||
public function sourceID() {
|
||||
$idField = $this->form->Fields()->dataFieldByName('ID');
|
||||
|
||||
// disabled as it conflicts with scaffolded formfields, and not strictly necessary
|
||||
// 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
|
||||
return ($idField) ? $idField->Value() : (isset($_REQUEST['ctf']['ID']) ? $_REQUEST['ctf']['ID'] : null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function AddLink() {
|
||||
return Controller::join_links($this->Link(), 'add');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FieldList
|
||||
*/
|
||||
public function createFieldList() {
|
||||
$fieldset = new FieldList();
|
||||
foreach($this->fieldTypes as $key => $fieldType){
|
||||
$fieldset->push(new $fieldType($key));
|
||||
}
|
||||
return $fieldset;
|
||||
}
|
||||
|
||||
public function setController($controller) {
|
||||
$this->controller = $controller;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTemplatePopup($template) {
|
||||
$this->templatePopup = $template;
|
||||
return $this;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the object-specific fields for the given record, to be shown in the detail pop-up
|
||||
*
|
||||
* This won't include all the CTF-specific 'plumbing; this method is called by self::getFieldsFor()
|
||||
* and the result is then processed further to get the actual FieldList for the form.
|
||||
*
|
||||
* The default implementation of this processes the value of $this->detailFormFields; consequently, if you want to
|
||||
* set the value of the fields to something that $this->detailFormFields doesn't allow, you can do so by overloading
|
||||
* this method.
|
||||
*/
|
||||
public function getCustomFieldsFor($childData) {
|
||||
if($this->detailFormFields instanceof FieldList) {
|
||||
return $this->detailFormFields;
|
||||
}
|
||||
|
||||
$fieldsMethod = $this->detailFormFields;
|
||||
|
||||
if(!is_string($fieldsMethod)) {
|
||||
$this->detailFormFields = 'getCMSFields';
|
||||
$fieldsMethod = 'getCMSFields';
|
||||
}
|
||||
|
||||
if(!$childData->hasMethod($fieldsMethod)) {
|
||||
$fieldsMethod = 'getCMSFields';
|
||||
}
|
||||
|
||||
return $childData->$fieldsMethod();
|
||||
}
|
||||
|
||||
public function getFieldsFor($childData) {
|
||||
$detailFields = $this->getCustomFieldsFor($childData);
|
||||
|
||||
// the ID field confuses the Controller-logic in finding the right view for ReferencedField
|
||||
$detailFields->removeByName('ID');
|
||||
|
||||
// only add childID if we're not adding a record
|
||||
if($childData->ID) {
|
||||
$detailFields->push(new HiddenField('ctf[childID]', '', $childData->ID));
|
||||
}
|
||||
|
||||
/* TODO: Figure out how to implement this
|
||||
if($this->getParentClass()) {
|
||||
$detailFields->push(new HiddenField('ctf[parentClass]', '', $this->getParentClass()));
|
||||
|
||||
// Hack for model admin: model admin will have included a dropdown for the relation itself
|
||||
$parentIdName = $this->getParentIdName($this->getParentClass(), $this->sourceClass());
|
||||
if($parentIdName) {
|
||||
$detailFields->removeByName($parentIdName);
|
||||
$detailFields->push(new HiddenField($parentIdName, '', $this->sourceID()));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return $detailFields;
|
||||
}
|
||||
|
||||
public function getValidatorFor($childData) {
|
||||
// if no custom validator is set, and there's on present on the object (e.g. Member), use it
|
||||
if(!isset($this->detailFormValidator) && $childData->hasMethod('getValidator')) {
|
||||
$this->detailFormValidator = $childData->getValidator();
|
||||
}
|
||||
return $this->detailFormValidator;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public function add() {
|
||||
if(!$this->can('add')) return;
|
||||
|
||||
return $this->customise(array(
|
||||
'DetailForm' => $this->AddForm(),
|
||||
))->renderWith($this->templatePopup);
|
||||
}
|
||||
|
||||
public function AddForm($childID = null) {
|
||||
$className = $this->sourceClass();
|
||||
$childData = new $className();
|
||||
|
||||
$fields = $this->getFieldsFor($childData);
|
||||
$validator = $this->getValidatorFor($childData);
|
||||
|
||||
$form = new $this->popupClass(
|
||||
$this,
|
||||
'AddForm',
|
||||
$fields,
|
||||
$validator,
|
||||
false,
|
||||
$childData
|
||||
);
|
||||
|
||||
$form->loadDataFrom($childData);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.1
|
||||
*/
|
||||
public function setRelationAutoSetting($value) {
|
||||
Deprecation::notice('3.0', 'Manipulate the DataList instead.');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* which in turn saves the record.
|
||||
*
|
||||
* This is for adding new item records. {@link ComplexTableField_ItemRequest::saveComplexTableField()}
|
||||
*
|
||||
* @see Form::ReferencedField
|
||||
*/
|
||||
public function saveComplexTableField($data, $form, $params) {
|
||||
$className = $this->sourceClass();
|
||||
$childData = new $className();
|
||||
$form->saveInto($childData);
|
||||
|
||||
try {
|
||||
$childData->write();
|
||||
} catch(ValidationException $e) {
|
||||
$form->sessionMessage($e->getResult()->message(), 'bad');
|
||||
return Controller::curr()->redirectBack();
|
||||
}
|
||||
|
||||
// Save this item into the given relationship
|
||||
$this->getDataList()->add($childData);
|
||||
|
||||
$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
|
||||
|
||||
$closeLink = sprintf(
|
||||
'<small><a href="%s" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
|
||||
$referrer,
|
||||
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
|
||||
);
|
||||
|
||||
$editLink = Controller::join_links($this->Link(), 'item/' . $childData->ID . '/edit');
|
||||
|
||||
$message = _t(
|
||||
'ComplexTableField.SUCCESSADD2', 'Added {name}',
|
||||
array('name' => $childData->singular_name())
|
||||
);
|
||||
$message .= '<a href="' . $editLink . '">' . $childData->Title . '</a>' . $closeLink;
|
||||
|
||||
$form->sessionMessage($message, 'good');
|
||||
|
||||
return Controller::curr()->redirectBack();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Tie this into ComplexTableField_Item better.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ComplexTableField_ItemRequest extends TableListField_ItemRequest {
|
||||
protected $ctf;
|
||||
protected $itemID;
|
||||
protected $methodName;
|
||||
|
||||
static $url_handlers = array(
|
||||
'$Action!' => '$Action',
|
||||
'' => 'index',
|
||||
);
|
||||
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links($this->ctf->Link(), '/item/', $this->itemID, $action);
|
||||
}
|
||||
|
||||
public function index() {
|
||||
return $this->show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Just a hook, processed in {DetailForm()}
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function show() {
|
||||
if($this->ctf->Can('show') !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->methodName = "show";
|
||||
return $this->renderWith($this->ctf->templatePopup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a 1-element data object set that can be used for pagination.
|
||||
*/
|
||||
/* this doesn't actually work :-(
|
||||
public function Paginator() {
|
||||
$paginatingSet = new ArrayList(array($this->dataObj()));
|
||||
$start = isset($_REQUEST['ctf']['start']) ? $_REQUEST['ctf']['start'] : 0;
|
||||
$paginatingSet->setPageLimits($start, 1, $this->ctf->TotalCount());
|
||||
return $paginatingSet;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Just a hook, processed in {DetailForm()}
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function edit() {
|
||||
if($this->ctf->Can('edit') !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->methodName = "edit";
|
||||
|
||||
return $this->renderWith($this->ctf->templatePopup);
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
$this->ctf->getDataList()->removeByID($this->itemID);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the data object being manipulated
|
||||
*/
|
||||
public function dataObj() {
|
||||
// used to discover fields if requested and for population of field
|
||||
if(is_numeric($this->itemID)) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param int $childID
|
||||
*/
|
||||
public function DetailForm($childID = null) {
|
||||
$childData = $this->dataObj();
|
||||
|
||||
$fields = $this->ctf->getFieldsFor($childData);
|
||||
$validator = $this->ctf->getValidatorFor($childData);
|
||||
$readonly = ($this->methodName == "show");
|
||||
|
||||
$form = new $this->ctf->popupClass(
|
||||
$this,
|
||||
"DetailForm",
|
||||
$fields,
|
||||
$validator,
|
||||
$readonly,
|
||||
$childData
|
||||
);
|
||||
// Don't use ComplexTableField_Popup.ss
|
||||
$form->setTemplate('Form');
|
||||
|
||||
$form->loadDataFrom($childData);
|
||||
if ($readonly) $form->makeReadonly();
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* which in turn saves the record.
|
||||
*
|
||||
* This is for editing existing item records. {@link ComplexTableField::saveComplexTableField()}
|
||||
*
|
||||
* @see Form::ReferencedField
|
||||
*/
|
||||
public function saveComplexTableField($data, $form, $request) {
|
||||
$dataObject = $this->dataObj();
|
||||
|
||||
try {
|
||||
$form->saveInto($dataObject);
|
||||
$dataObject->write();
|
||||
} catch(ValidationException $e) {
|
||||
$form->sessionMessage($e->getResult()->message(), 'bad');
|
||||
return Controller::curr()->redirectBack();
|
||||
}
|
||||
|
||||
// Save this item into the given relationship
|
||||
$this->ctf->getDataList()->add($dataObject);
|
||||
|
||||
$referrer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
|
||||
|
||||
$closeLink = sprintf(
|
||||
'<small><a href="%s" onclick="javascript:window.top.GB_hide(); return false;">(%s)</a></small>',
|
||||
$referrer,
|
||||
_t('ComplexTableField.CLOSEPOPUP', 'Close Popup')
|
||||
);
|
||||
$message = sprintf(
|
||||
_t('ComplexTableField.SUCCESSEDIT', 'Saved %s %s %s'),
|
||||
$dataObject->singular_name(),
|
||||
'<a href="' . $this->Link('edit') . '">"' . htmlspecialchars($dataObject->Title, ENT_QUOTES) . '"</a>',
|
||||
$closeLink
|
||||
);
|
||||
|
||||
$form->sessionMessage($message, 'good');
|
||||
|
||||
return Controller::curr()->redirectBack();
|
||||
}
|
||||
|
||||
public function PopupCurrentItem() {
|
||||
return $_REQUEST['ctf']['start']+1;
|
||||
}
|
||||
|
||||
public function PopupFirstLink() {
|
||||
$this->ctf->LinkToItem();
|
||||
|
||||
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start'])
|
||||
|| $_REQUEST['ctf']['start'] == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = 0;
|
||||
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
|
||||
}
|
||||
|
||||
public function PopupLastLink() {
|
||||
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start'])
|
||||
|| $_REQUEST['ctf']['start'] == $this->TotalCount()-1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = $this->TotalCount - 1;
|
||||
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
|
||||
}
|
||||
|
||||
public function PopupNextLink() {
|
||||
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start'])
|
||||
|| $_REQUEST['ctf']['start'] == $this->TotalCount()-1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = $_REQUEST['ctf']['start'] + 1;
|
||||
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
|
||||
}
|
||||
|
||||
public function PopupPrevLink() {
|
||||
if(!isset($_REQUEST['ctf']['start']) || !is_numeric($_REQUEST['ctf']['start'])
|
||||
|| $_REQUEST['ctf']['start'] == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$start = $_REQUEST['ctf']['start'] - 1;
|
||||
return Controller::join_links($this->Link(), "$this->methodName?ctf[start]={$start}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Method handles pagination in asset popup.
|
||||
*
|
||||
* @return Object SS_List
|
||||
*/
|
||||
|
||||
public function Pagination() {
|
||||
$this->pageSize = 9;
|
||||
$currentItem = $this->PopupCurrentItem();
|
||||
$result = new ArrayList();
|
||||
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;
|
||||
$links['link'] = Controller::join_links($this->Link() . "$this->methodName?ctf[start]={$start}");
|
||||
$links['number'] = $i;
|
||||
$links['active'] = $i == $currentItem ? false : true;
|
||||
$result->push(new ArrayData($links));
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function ShowPagination() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Utility
|
||||
* #################################
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manually overwrites the parent-ID relations.
|
||||
* @see setParentClass()
|
||||
*
|
||||
* @param String $str Example: FamilyID (when one Individual has_one Family)
|
||||
*/
|
||||
public function setParentIdName($str) {
|
||||
throw new Exception("setParentIdName is no longer necessary");
|
||||
}
|
||||
|
||||
public function setTemplatePopup($template) {
|
||||
$this->templatePopup = $template;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Single row of a {@link ComplexTableField}.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ComplexTableField_Item extends TableListField_Item {
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links($this->parent->Link(), '/item/', $this->item->ID, $action);
|
||||
}
|
||||
|
||||
public function EditLink() {
|
||||
return Controller::join_links($this->Link(), "edit");
|
||||
}
|
||||
|
||||
public function ShowLink() {
|
||||
return Controller::join_links($this->Link(), "show");
|
||||
}
|
||||
|
||||
public function DeleteLink() {
|
||||
return Controller::join_links($this->Link(), "delete");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param String $action
|
||||
* @return boolean
|
||||
*/
|
||||
public function IsDefaultAction($action) {
|
||||
return ($action == $this->parent->defaultAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ComplexTablefield_popup is rendered with a lightbox and can load a more
|
||||
* detailed view of the source class your presenting.
|
||||
* You can customise the fields and requirements as well as any
|
||||
* permissions you might need.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ComplexTableField_Popup extends Form {
|
||||
protected $sourceClass;
|
||||
|
||||
protected $dataObject;
|
||||
|
||||
public function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
|
||||
$this->dataObject = $dataObject;
|
||||
|
||||
|
||||
$actions = new FieldList();
|
||||
if(!$readonly) {
|
||||
$actions->push(
|
||||
FormAction::create(
|
||||
"saveComplexTableField",
|
||||
_t('CMSMain.SAVE', 'Save')
|
||||
)
|
||||
->addExtraClass('save ss-ui-action-constructive')
|
||||
->setUseButtonTag(true)
|
||||
->setAttribute('data-icon', 'accept')
|
||||
);
|
||||
}
|
||||
|
||||
parent::__construct($controller, $name, $fields, $actions, $validator);
|
||||
|
||||
if(!$this->dataObject->canEdit()) $this->makeReadonly();
|
||||
}
|
||||
|
||||
public function forTemplate() {
|
||||
$ret = parent::forTemplate();
|
||||
|
||||
Requirements::css(FRAMEWORK_DIR . '/css/ComplexTableField_popup.css');
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/prototype/prototype.js");
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/behaviour/behaviour.js");
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/scriptaculous/scriptaculous.js");
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/thirdparty/scriptaculous/scriptaculous/controls.js");
|
||||
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/javascript/ComplexTableField_popup.js");
|
||||
|
||||
// Append requirements from instance callbacks
|
||||
$parent = $this->getParentController();
|
||||
if($parent instanceof ComplexTableField) {
|
||||
$callback = $parent->requirementsForPopupCallback;
|
||||
} else {
|
||||
$callback = $parent->getParentController()->requirementsForPopupCallback;
|
||||
}
|
||||
if($callback) call_user_func($callback, $this);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getTemplate() {
|
||||
return 'Form';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ComplexTableField_ItemRequest
|
||||
*/
|
||||
public function getParentController() {
|
||||
return $this->controller;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,181 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ComplexTableField designed to edit a has_many join.
|
||||
*
|
||||
* This field allows you to show a 1-to-many relation with a group of DataObjects as a (readonly) tabular list. Its
|
||||
* most useful when you want to manage the relationship itself thanks the **check boxes** present on each line of the
|
||||
* table.
|
||||
*
|
||||
* Moreover, you can not do any mistake anymore in the relation by checking a DataObject already linked with another
|
||||
* of the parent class.
|
||||
*
|
||||
* See {@link ComplexTableField} for more documentation on the base-class.
|
||||
*
|
||||
* <b>Usage</b>
|
||||
*
|
||||
* <code>
|
||||
* $tablefield = new HasManyComplexTableField(
|
||||
* $this,
|
||||
* 'MyFruits',
|
||||
* 'Fruit',
|
||||
* array(
|
||||
* 'Name' => 'Name',
|
||||
* 'Color' => 'Color'
|
||||
* ),
|
||||
* 'getCMSFields_forPopup'
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
* Notice: You still have different ways to customize the popup window as in the parent-class
|
||||
* {@link ComplexTableField}.
|
||||
*
|
||||
* @see http://doc.silverstripe.org/tutorial/5-dataobject-relationship-management
|
||||
*
|
||||
* @deprecated 3.1 Use GridField with GridFieldConfig_RelationEditor
|
||||
*
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class HasManyComplexTableField extends ComplexTableField {
|
||||
|
||||
public $joinField;
|
||||
|
||||
protected $addTitle;
|
||||
|
||||
// If you change the value, do not forget to change it also in the JS file
|
||||
protected $htmlListEndName = 'CheckedList';
|
||||
|
||||
// If you change the value, do not forget to change it also in the JS file
|
||||
protected $htmlListField = 'selected';
|
||||
|
||||
public $template = 'RelationComplexTableField';
|
||||
|
||||
public $itemClass = 'HasManyComplexTableField_Item';
|
||||
|
||||
protected $relationAutoSetting = false;
|
||||
|
||||
public function __construct($controller, $name, $sourceClass, $fieldList = null, $detailFormFields = null,
|
||||
$sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
|
||||
|
||||
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields,
|
||||
$sourceFilter, $sourceSort, $sourceJoin);
|
||||
|
||||
Deprecation::notice('3.0', 'Use GridField with GridFieldConfig_RelationEditor', Deprecation::SCOPE_CLASS);
|
||||
|
||||
$this->Markable = true;
|
||||
|
||||
if($controllerClass = $this->controllerClass()) {
|
||||
$this->joinField = $this->getParentIdName($controllerClass, $this->sourceClass);
|
||||
if(!$this->joinField) {
|
||||
user_error("Can't find a has_one relationship from '$this->sourceClass' to '$controllerClass'",
|
||||
E_USER_WARNING);
|
||||
}
|
||||
} else {
|
||||
user_error("Can't figure out the data class of $controller", E_USER_WARNING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function FieldHolder($properties = array()) {
|
||||
$ret = parent::FieldHolder($properties);
|
||||
|
||||
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||
Requirements::javascript(FRAMEWORK_DIR . "/javascript/HasManyFileField.js");
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/RelationComplexTableField.js');
|
||||
Requirements::css(FRAMEWORK_DIR . '/css/HasManyFileField.css');
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to determine the DataObject that this field is built on top of
|
||||
*/
|
||||
public function controllerClass() {
|
||||
if($this->controller instanceof DataObject) return $this->controller->class;
|
||||
elseif($this->controller instanceof Controller) return $this->controller->data()->class;
|
||||
}
|
||||
|
||||
public function getControllerID() {
|
||||
return $this->controller->ID;
|
||||
}
|
||||
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
$fieldName = $this->name;
|
||||
$saveDest = $record->$fieldName();
|
||||
|
||||
if(!$saveDest) {
|
||||
user_error("HasManyComplexTableField::saveInto() Field $fieldName not found on $record->class.$record->ID"
|
||||
, E_USER_ERROR);
|
||||
}
|
||||
|
||||
$items = array();
|
||||
|
||||
if($list = $this->value[ $this->htmlListField ]) {
|
||||
if($list != 'undefined')
|
||||
$items = explode(',', $list);
|
||||
}
|
||||
|
||||
$saveDest->setByIDList($items);
|
||||
}
|
||||
|
||||
public function setAddTitle($addTitle) {
|
||||
if(is_string($addTitle))
|
||||
$this->addTitle = $addTitle;
|
||||
}
|
||||
|
||||
public function Title() {
|
||||
return $this->addTitle ? $this->addTitle : parent::Title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the IDs of the selected items, in a has_many or many_many relation
|
||||
*/
|
||||
public function selectedItemIDs() {
|
||||
$fieldName = $this->name;
|
||||
$selectedItems = $this->form->getRecord()->$fieldName();
|
||||
$itemIDs = array();
|
||||
foreach($selectedItems as $item) $itemIDs[] = $item->ID;
|
||||
return $itemIDs;
|
||||
}
|
||||
|
||||
public function ExtraData() {
|
||||
$items = array();
|
||||
|
||||
$list = implode(',', $this->selectedItemIDs());
|
||||
$inputId = $this->id() . '_' . $this->htmlListEndName;
|
||||
return <<<HTML
|
||||
<input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$list"/>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single record of a {@link HasManyComplexTableField} field.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class HasManyComplexTableField_Item extends ComplexTableField_Item {
|
||||
|
||||
public function MarkingCheckbox() {
|
||||
$name = $this->parent->getName() . '[]';
|
||||
|
||||
if(!$this->parent->joinField) {
|
||||
user_error("joinField not set in HasManyComplexTableField '{$this->parent->name}'", E_USER_WARNING);
|
||||
return null;
|
||||
}
|
||||
|
||||
$joinVal = $this->item->{$this->parent->joinField};
|
||||
$parentID = $this->parent->getControllerID();
|
||||
|
||||
if($this->parent->IsReadOnly || ($joinVal > 0 && $joinVal != $parentID))
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
disabled=\"disabled\"/>";
|
||||
else if($joinVal == $parentID)
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
checked=\"checked\"/>";
|
||||
else
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"/>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,109 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* ComplexTableField with a radio button column, designed to edit a has_one join.
|
||||
*
|
||||
* This [RelationTable](RelationTable) allows you to show a **1-to-1** or **1-to-many** relation with a group of
|
||||
* DataObjects as a (readonly) tabular list (similiar to [ComplexTableField](ComplexTableField)). Its most useful when
|
||||
* you want to manage the relationship itself thanks the **radio buttons** present on each line of the table.
|
||||
*
|
||||
* Moreover, you have the possibility to uncheck a radio button in order to make the relation as null.
|
||||
*
|
||||
* <b>Usage</b>
|
||||
*
|
||||
* <code>
|
||||
* $tablefield = new HasOneComplexTableField(
|
||||
* $this,
|
||||
* 'MyOnlyFruit',
|
||||
* 'Fruit',
|
||||
* array(
|
||||
* 'Name' => 'Name',
|
||||
* 'Color' => 'Color'
|
||||
* ),
|
||||
* 'getCMSFields_forPopup'
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
* **Notice** : You still have different ways to customize the popup window as in the parent-class
|
||||
* [ComplexTableField](ComplexTableField).
|
||||
*
|
||||
* This field is made to manage a **has_one** relation. In the SilverStripe relation between DataObjects, you can use
|
||||
* this relation for **1-to-1** and **1-to-many** relations.
|
||||
* By default, a HasOneComplexTableField manages a **1-to-many** relation. If you want to specify that the relation
|
||||
* that you manage is a **1-to-1** relation, add this code :
|
||||
*
|
||||
* <code>
|
||||
* $tablefield->setOneToOne();
|
||||
* </code>
|
||||
*
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class HasOneComplexTableField extends HasManyComplexTableField {
|
||||
|
||||
public $itemClass = 'HasOneComplexTableField_Item';
|
||||
|
||||
public $isOneToOne = false;
|
||||
|
||||
public function getParentIdName($parentClass, $childClass) {
|
||||
return $this->getParentIdNameRelation($parentClass, $childClass, 'has_one');
|
||||
}
|
||||
|
||||
public function getControllerJoinID() {
|
||||
return $this->controller->{$this->joinField};
|
||||
}
|
||||
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
$fieldName = $this->name;
|
||||
$fieldNameID = $fieldName . 'ID';
|
||||
|
||||
$record->$fieldNameID = 0;
|
||||
if($val = $this->value[ $this->htmlListField ]) {
|
||||
if($val != 'undefined')
|
||||
$record->$fieldNameID = $val;
|
||||
}
|
||||
|
||||
$record->write();
|
||||
}
|
||||
|
||||
public function setOneToOne() {
|
||||
$this->isOneToOne = true;
|
||||
}
|
||||
|
||||
public function isChildSet($childID) {
|
||||
return DataObject::get($this->controllerClass(), '"' . $this->joinField . "\" = '$childID'");
|
||||
}
|
||||
|
||||
public function ExtraData() {
|
||||
$val = $this->getControllerJoinID() ? $this->getControllerJoinID() : '';
|
||||
$inputId = $this->id() . '_' . $this->htmlListEndName;
|
||||
return <<<HTML
|
||||
<input id="$inputId" name="{$this->name}[{$this->htmlListField}]" type="hidden" value="$val"/>
|
||||
HTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single record of a {@link HasOneComplexTableField} field.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class HasOneComplexTableField_Item extends ComplexTableField_Item {
|
||||
|
||||
public function MarkingCheckbox() {
|
||||
$name = $this->parent->getName() . '[]';
|
||||
|
||||
$isOneToOne = $this->parent->isOneToOne;
|
||||
$joinVal = $this->parent->getControllerJoinID();
|
||||
$childID = $this->item->ID;
|
||||
|
||||
if($this->parent->IsReadOnly || ($isOneToOne && $joinVal != $childID && $this->parent->isChildSet($childID)))
|
||||
return "<input class=\"radio\" type=\"radio\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
disabled=\"disabled\"/>";
|
||||
else if($joinVal == $childID)
|
||||
return "<input class=\"radio\" type=\"radio\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
checked=\"checked\"/>";
|
||||
else
|
||||
return "<input class=\"radio\" type=\"radio\" name=\"$name\" value=\"{$this->item->ID}\"/>";
|
||||
}
|
||||
}
|
||||
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Special ComplexTableField for editing a many_many relation.
|
||||
*
|
||||
* This field allows you to show a **many-to-many** relation with a group of
|
||||
* DataObjects as a (readonly) tabular list (similiar to {@link ComplexTableField}).
|
||||
* Its most useful when you want to manage the relationship itself
|
||||
* thanks to the check boxes present on each line of the table.
|
||||
*
|
||||
* See {@link ComplexTableField} for more documentation on the base-class.
|
||||
* See {@link HasManyComplexTableField} for more documentation on the relation table base-class.
|
||||
*
|
||||
* Note: This class relies on the fact that both sides of the relation have database tables.
|
||||
* If you are only creating a class as a logical extension (that is, it doesn't have any database fields),
|
||||
* then you will need to create a dummy static $db array because SilverStripe won't create a database
|
||||
* table unless needed.
|
||||
*
|
||||
* <b>Usage</b>
|
||||
*
|
||||
* <code>
|
||||
* $tablefield = new ManyManyComplexTableField(
|
||||
* $this,
|
||||
* 'MyFruits',
|
||||
* 'Fruit',
|
||||
* array(
|
||||
* 'Name' => 'Name',
|
||||
* 'Color' => 'Color'
|
||||
* ),
|
||||
* 'getCMSFields_forPopup'
|
||||
* );
|
||||
* </code>
|
||||
*
|
||||
* @deprecated 3.1 Use GridField with GridFieldConfig_RelationEditor
|
||||
*
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ManyManyComplexTableField extends HasManyComplexTableField {
|
||||
|
||||
private $manyManyParentClass;
|
||||
|
||||
public $itemClass = 'ManyManyComplexTableField_Item';
|
||||
|
||||
public function __construct($controller, $name, $sourceClass, $fieldList = null, $detailFormFields = null,
|
||||
$sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
|
||||
|
||||
Deprecation::notice('3.0', 'Use GridField with GridFieldConfig_RelationEditor', Deprecation::SCOPE_CLASS);
|
||||
|
||||
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields,
|
||||
$sourceFilter, $sourceSort, $sourceJoin);
|
||||
|
||||
$classes = array_reverse(ClassInfo::ancestry($this->controllerClass()));
|
||||
foreach($classes as $class) {
|
||||
$singleton = singleton($class);
|
||||
$manyManyRelations = $singleton->uninherited('many_many', true);
|
||||
if(isset($manyManyRelations) && array_key_exists($this->name, $manyManyRelations)) {
|
||||
$this->manyManyParentClass = $class;
|
||||
$manyManyTable = $class . '_' . $this->name;
|
||||
break;
|
||||
}
|
||||
$belongsManyManyRelations = $singleton->uninherited( 'belongs_many_many', true );
|
||||
if( isset( $belongsManyManyRelations ) && array_key_exists( $this->name, $belongsManyManyRelations ) ) {
|
||||
$this->manyManyParentClass = $class;
|
||||
$manyManyTable = $belongsManyManyRelations[$this->name] . '_' . $this->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->sourceClass);
|
||||
$source = array_shift($tableClasses);
|
||||
$sourceField = $this->sourceClass;
|
||||
if($this->manyManyParentClass == $sourceField)
|
||||
$sourceField = 'Child';
|
||||
$parentID = $this->controller->ID;
|
||||
|
||||
$this->sourceJoin .= " LEFT JOIN \"$manyManyTable\"
|
||||
ON (\"$source\".\"ID\" = \"$manyManyTable\".\"{$sourceField}ID\"
|
||||
AND \"{$this->manyManyParentClass}ID\" = '$parentID')";
|
||||
|
||||
$this->joinField = 'Checked';
|
||||
}
|
||||
|
||||
public function getQuery() {
|
||||
$query = parent::getQuery();
|
||||
$query->selectField("CASE WHEN \"{$this->manyManyParentClass}ID\" IS NULL THEN '0' ELSE '1' END", "Checked");
|
||||
$query->groupby[] = "\"{$this->manyManyParentClass}ID\""; // necessary for Postgres
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getParentIdName($parentClass, $childClass) {
|
||||
return $this->getParentIdNameRelation($parentClass, $childClass, 'many_many');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One record in a {@link ManyManyComplexTableField}.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class ManyManyComplexTableField_Item extends ComplexTableField_Item {
|
||||
|
||||
public function MarkingCheckbox() {
|
||||
$name = $this->parent->getName() . '[]';
|
||||
|
||||
if($this->parent->IsReadOnly)
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
disabled=\"disabled\"/>";
|
||||
else if($this->item->{$this->parent->joinField})
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"
|
||||
checked=\"checked\"/>";
|
||||
else
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\"/>";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,724 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* TableField behaves in the same manner as TableListField, however allows the addition of
|
||||
* fields and editing of attributes specified, and filtering results.
|
||||
*
|
||||
* Caution: If you insert DropdownFields in the fieldTypes-array, make sure they have an empty first option.
|
||||
* Otherwise the saving can't determine if a new row should really be saved.
|
||||
*
|
||||
* Caution: TableField relies on {@FormResponse} to reload the field after it is saved.
|
||||
* A TableField-instance should never be saved twice without reloading, because otherwise it
|
||||
* can't determine if a field is new (=create) or existing (=update), and will produce duplicates.
|
||||
*
|
||||
* IMPORTANT: This class is about to be deprecated in favour of a new GridFieldEditableColumns component,
|
||||
* see http://open.silverstripe.org/ticket/7045
|
||||
*
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
|
||||
class TableField extends TableListField {
|
||||
|
||||
protected $fieldList;
|
||||
|
||||
/**
|
||||
* A "Field = Value" filter can be specified by setting $this->filterField and $this->filterValue. This has the
|
||||
* advantage of auto-populating new records
|
||||
*/
|
||||
protected $filterField = null;
|
||||
|
||||
/**
|
||||
* A "Field = Value" filter can be specified by setting $this->filterField and $this->filterValue. This has the
|
||||
* advantage of auto-populating new records
|
||||
*/
|
||||
protected $filterValue = null;
|
||||
|
||||
/**
|
||||
* @var $fieldTypes FieldList
|
||||
* Caution: Use {@setExtraData()} instead of manually adding HiddenFields if you want to
|
||||
* preset relations or other default data.
|
||||
*/
|
||||
protected $fieldTypes;
|
||||
|
||||
/**
|
||||
* @var $template string Template-Overrides
|
||||
*/
|
||||
protected $template = "TableField";
|
||||
|
||||
/**
|
||||
* @var $extraData array Any extra data that need to be included, e.g. to retain
|
||||
* has-many relations. Format: array('FieldName' => 'Value')
|
||||
*/
|
||||
protected $extraData;
|
||||
|
||||
protected $tempForm;
|
||||
|
||||
/**
|
||||
* Influence output without having to subclass the template.
|
||||
*/
|
||||
protected $permissions = array(
|
||||
"edit",
|
||||
"delete",
|
||||
"add",
|
||||
//"export",
|
||||
);
|
||||
|
||||
public $transformationConditions = array();
|
||||
|
||||
/**
|
||||
* @var $requiredFields array Required fields as a numerical array.
|
||||
* Please use an instance of Validator on the including
|
||||
* form.
|
||||
*/
|
||||
protected $requiredFields = null;
|
||||
|
||||
/**
|
||||
* Shows a row of empty fields for adding a new record
|
||||
* (turned on by default).
|
||||
* Please use {@link TableField::$permissions} to control
|
||||
* if the "add"-functionality incl. button is shown at all.
|
||||
*
|
||||
* @param boolean $showAddRow
|
||||
*/
|
||||
public $showAddRow = true;
|
||||
|
||||
/**
|
||||
* @param $name string The fieldname
|
||||
* @param $sourceClass string The source class of this field
|
||||
* @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
|
||||
* @param $fieldTypes array An array of field types of fieldname => fieldType (eg. formfield). Do not use for
|
||||
* extra data/hiddenfields.
|
||||
* @param $filterField string The field to filter by. Give the filter value in $sourceFilter. The value will
|
||||
* automatically be set on new records.
|
||||
* @param $sourceFilter string If $filterField has a value, then this is the value to filter by. Otherwise, it is
|
||||
* a SQL filter expression.
|
||||
* @param $editExisting boolean (Note: Has to stay on this position for legacy reasons)
|
||||
* @param $sourceSort string
|
||||
* @param $sourceJoin string
|
||||
*/
|
||||
public function __construct($name, $sourceClass, $fieldList = null, $fieldTypes, $filterField = null,
|
||||
$sourceFilter = null, $editExisting = true, $sourceSort = null, $sourceJoin = null) {
|
||||
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->filterField = $filterField;
|
||||
|
||||
$this->editExisting = $editExisting;
|
||||
|
||||
// If we specify filterField, then an implicit source filter of "filterField = sourceFilter" is used.
|
||||
if($filterField) {
|
||||
$this->filterValue = $sourceFilter;
|
||||
$sourceFilter = "\"$filterField\" = '" . Convert::raw2sql($sourceFilter) . "'";
|
||||
}
|
||||
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the headings on the template
|
||||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Headings() {
|
||||
$i=0;
|
||||
foreach($this->fieldList as $fieldName => $fieldTitle) {
|
||||
$extraClass = "col".$i;
|
||||
$class = $this->fieldTypes[$fieldName];
|
||||
if(is_object($class)) $class = "";
|
||||
$class = $class." ".$extraClass;
|
||||
$headings[] = new ArrayData(array("Name" => $fieldName, "Title" => $fieldTitle, "Class" => $class));
|
||||
$i++;
|
||||
}
|
||||
return new ArrayList($headings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of columns needed for colspans
|
||||
* used in template
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function ItemCount() {
|
||||
return count($this->fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the items from {@link sourceItems()} using the encapsulation object.
|
||||
* If the field value has been set as an array (e.g. after a failed validation),
|
||||
* it generates the rows from array data instead.
|
||||
* Used in the formfield template to iterate over each row.
|
||||
*
|
||||
* @return SS_List Collection of {@link TableField_Item}
|
||||
*/
|
||||
public function Items() {
|
||||
// holds TableField_Item instances
|
||||
$items = new ArrayList();
|
||||
|
||||
$sourceItems = $this->sourceItems();
|
||||
|
||||
// either load all rows from the field value,
|
||||
// (e.g. when validation failed), or from sourceItems()
|
||||
if($this->value) {
|
||||
if(!$sourceItems) $sourceItems = new ArrayList();
|
||||
|
||||
// get an array keyed by rows, rather than values
|
||||
$rows = $this->sortData(ArrayLib::invert($this->value));
|
||||
// ignore all rows which are already saved
|
||||
if(isset($rows['new'])) {
|
||||
if($sourceItems instanceof DataList) {
|
||||
$sourceItems = new ArrayList($sourceItems->toArray());
|
||||
}
|
||||
|
||||
$newRows = $this->sortData($rows['new']);
|
||||
// iterate over each value (not each row)
|
||||
$i = 0;
|
||||
foreach($newRows as $idx => $newRow){
|
||||
// set a pseudo-ID
|
||||
$newRow['ID'] = "new";
|
||||
|
||||
// unset any extradata
|
||||
foreach($newRow as $k => $v){
|
||||
if($this->extraData && array_key_exists($k, $this->extraData)){
|
||||
unset($newRow[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
// generate a temporary DataObject container (not saved in the database)
|
||||
$sourceClass = $this->sourceClass();
|
||||
$sourceItems->add(new $sourceClass($newRow));
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate a new TableField_Item instance from each collected item
|
||||
if($sourceItems) foreach($sourceItems as $sourceItem) {
|
||||
$items->push($this->generateTableFieldItem($sourceItem));
|
||||
}
|
||||
|
||||
// add an empty TableField_Item for a single "add row"
|
||||
if($this->showAddRow && $this->Can('add')) {
|
||||
$items->push(new TableField_Item(null, $this, null, $this->fieldTypes, true));
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new {@link TableField} instance
|
||||
* by loading a FieldList for this row into a temporary form.
|
||||
*
|
||||
* @param DataObject $dataObj
|
||||
* @return TableField_Item
|
||||
*/
|
||||
protected function generateTableFieldItem($dataObj) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$form = new Form(
|
||||
$this,
|
||||
null,
|
||||
$this->FieldSetForRow(),
|
||||
new FieldList()
|
||||
);
|
||||
$form->loadDataFrom($dataObj);
|
||||
|
||||
// Add the item to our new ArrayList, with a wrapper class.
|
||||
return new TableField_Item($dataObj, $this, $form, $this->fieldTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function FieldList() {
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Dataobjects contained in the field
|
||||
*/
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
// CMS sometimes tries to set the value to one.
|
||||
if(is_array($this->value)){
|
||||
$newFields = array();
|
||||
|
||||
// Sort into proper array
|
||||
$value = ArrayLib::invert($this->value);
|
||||
$dataObjects = $this->sortData($value, $record->ID);
|
||||
|
||||
// New fields are nested in their own sub-array, and need to be sorted separately
|
||||
if(isset($dataObjects['new']) && $dataObjects['new']) {
|
||||
$newFields = $this->sortData($dataObjects['new'], $record->ID);
|
||||
}
|
||||
|
||||
// Update existing fields
|
||||
// @todo Should this be in an else{} statement?
|
||||
$savedObjIds = $this->saveData($dataObjects, $this->editExisting);
|
||||
|
||||
// Save newly added record
|
||||
if($savedObjIds || $newFields) {
|
||||
$savedObjIds = $this->saveData($newFields,false);
|
||||
}
|
||||
|
||||
// Add the new records to the DataList
|
||||
if($savedObjIds) foreach($savedObjIds as $id => $status) {
|
||||
$this->getDataList()->add($id);
|
||||
}
|
||||
|
||||
// Update the internal source items cache
|
||||
$this->value = null;
|
||||
$items = $this->sourceItems();
|
||||
|
||||
// FormResponse::update_dom_id($this->id(), $this->FieldHolder());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all {@link FormField} instances necessary for a single row,
|
||||
* respecting the casting set in {@link $fieldTypes}.
|
||||
* Doesn't populate with any data. Optionally performs a readonly
|
||||
* transformation if {@link $IsReadonly} is set, or the current user
|
||||
* doesn't have edit permissions.
|
||||
*
|
||||
* @return FieldList
|
||||
*/
|
||||
public function FieldSetForRow() {
|
||||
$fieldset = new FieldList();
|
||||
if($this->fieldTypes){
|
||||
foreach($this->fieldTypes as $key => $fieldType) {
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
// using clone, otherwise we would just add stuff to the same field-instance
|
||||
$field = clone $fieldType;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
$field = new $fieldType($key);
|
||||
} else {
|
||||
$fieldName = $key;
|
||||
$fieldTitle = "";
|
||||
$field = eval("return new $fieldType;");
|
||||
}
|
||||
if($this->IsReadOnly || !$this->Can('edit')) {
|
||||
$field = $field->performReadonlyTransformation();
|
||||
}
|
||||
$fieldset->push($field);
|
||||
}
|
||||
}else{
|
||||
USER_ERROR("TableField::FieldSetForRow() - Fieldtypes were not specified",E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $fieldset;
|
||||
}
|
||||
|
||||
public function performReadonlyTransformation() {
|
||||
$clone = clone $this;
|
||||
$clone->permissions = array('show');
|
||||
$clone->setReadonly(true);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function performDisabledTransformation() {
|
||||
$clone = clone $this;
|
||||
$clone->setPermissions(array('show'));
|
||||
$clone->setDisabled(true);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for Form->callfieldmethod.
|
||||
*/
|
||||
public function getField($fieldName, $combinedFieldName = null) {
|
||||
$fieldSet = $this->FieldSetForRow();
|
||||
$field = $fieldSet->dataFieldByName($fieldName);
|
||||
if(!$field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($combinedFieldName) {
|
||||
$field->Name = $combinedFieldName;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on save, it creates the appropriate objects and writes them
|
||||
* to the database.
|
||||
*
|
||||
* @param SS_List $dataObjects
|
||||
* @param boolean $existingValues If set to TRUE, it tries to find existing objects
|
||||
* based on the database IDs passed as array keys in $dataObjects parameter.
|
||||
* If set to FALSE, it will always create new object (default: TRUE)
|
||||
* @return array Array of saved object IDs in the key, and the status ("Updated") in the value
|
||||
*/
|
||||
public function saveData($dataObjects, $existingValues = true) {
|
||||
if(!$dataObjects) return false;
|
||||
|
||||
$savedObjIds = array();
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
|
||||
// add hiddenfields
|
||||
if($this->extraData) {
|
||||
foreach($this->extraData as $fieldName => $fieldValue) {
|
||||
$fieldset->push(new HiddenField($fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
$form = new Form($this, null, $fieldset, new FieldList());
|
||||
|
||||
foreach ($dataObjects as $objectid => $fieldValues) {
|
||||
// 'new' counts as an empty column, don't save it
|
||||
if($objectid === "new") continue;
|
||||
|
||||
// extra data was creating fields, but
|
||||
if($this->extraData) {
|
||||
$fieldValues = array_merge( $this->extraData, $fieldValues );
|
||||
}
|
||||
|
||||
// either look for an existing object, or create a new one
|
||||
if($existingValues) {
|
||||
$obj = DataObject::get_by_id($this->sourceClass(), $objectid);
|
||||
} else {
|
||||
$sourceClass = $this->sourceClass();
|
||||
$obj = new $sourceClass();
|
||||
}
|
||||
|
||||
// Legacy: Use the filter as a predefined relationship-ID
|
||||
if($this->filterField && $this->filterValue) {
|
||||
$filterField = $this->filterField;
|
||||
$obj->$filterField = $this->filterValue;
|
||||
}
|
||||
|
||||
// Determine if there is changed data for saving
|
||||
$dataFields = array();
|
||||
foreach($fieldValues as $type => $value) {
|
||||
// if the field is an actual datafield (not a preset hiddenfield)
|
||||
if(is_array($this->extraData)) {
|
||||
if(!in_array($type, array_keys($this->extraData))) {
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
// all fields are real
|
||||
} else {
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
}
|
||||
$dataValues = ArrayLib::array_values_recursive($dataFields);
|
||||
// determine if any of the fields have a value (loose checking with empty())
|
||||
$hasData = false;
|
||||
foreach($dataValues as $value) {
|
||||
if(!empty($value)) $hasData = true;
|
||||
}
|
||||
|
||||
if($hasData) {
|
||||
$form->loadDataFrom($fieldValues, true);
|
||||
$form->saveInto($obj);
|
||||
|
||||
$objectid = $obj->write();
|
||||
|
||||
$savedObjIds[$objectid] = "Updated";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $savedObjIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Organises the data in the appropriate manner for saving
|
||||
*
|
||||
* @param array $data
|
||||
* @param int $recordID
|
||||
* @return array Collection of maps suitable to construct DataObjects
|
||||
*/
|
||||
public function sortData($data, $recordID = null) {
|
||||
if(!$data) return false;
|
||||
|
||||
$sortedData = array();
|
||||
|
||||
foreach($data as $field => $rowData) {
|
||||
$i = 0;
|
||||
if(!is_array($rowData)) continue;
|
||||
|
||||
foreach($rowData as $id => $value) {
|
||||
if($value == '$recordID') $value = $recordID;
|
||||
|
||||
if($value) $sortedData[$id][$field] = $value;
|
||||
|
||||
$i++;
|
||||
}
|
||||
|
||||
// TODO ADD stuff for removing rows with incomplete data
|
||||
}
|
||||
|
||||
return $sortedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $extraData array
|
||||
*/
|
||||
public function setExtraData($extraData) {
|
||||
$this->extraData = $extraData;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getExtraData() {
|
||||
return $this->extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the template to be rendered with
|
||||
*/
|
||||
public function FieldHolder($properties = array()) {
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/prototype/prototype.js");
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/behaviour/behaviour.js');
|
||||
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/TableListField.js');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/TableField.js');
|
||||
Requirements::css(FRAMEWORK_DIR . '/css/TableListField.css');
|
||||
|
||||
$obj = $properties ? $this->customise($properties) : $this;
|
||||
return $obj->renderWith($this->template);
|
||||
}
|
||||
|
||||
public function setTransformationConditions($conditions) {
|
||||
$this->transformationConditions = $conditions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function php($data) {
|
||||
$valid = true;
|
||||
|
||||
if($data['methodName'] != 'delete') {
|
||||
$items = $this->Items();
|
||||
if($items) foreach($items as $item) {
|
||||
foreach($item->Fields() as $field) {
|
||||
$valid = $field->validate($this) && $valid;
|
||||
}
|
||||
}
|
||||
|
||||
return $valid;
|
||||
} else {
|
||||
return $valid;
|
||||
}
|
||||
}
|
||||
|
||||
public function validate($validator) {
|
||||
$errorMessage = '';
|
||||
$valid = true;
|
||||
|
||||
// @todo should only contain new elements
|
||||
$items = $this->Items();
|
||||
if($items) foreach($items as $item) {
|
||||
foreach($item->Fields() as $field) {
|
||||
$valid = $field->validate($validator) && $valid;
|
||||
}
|
||||
}
|
||||
|
||||
//debug::show($this->form->Message());
|
||||
if($this->requiredFields&&$sourceItemsNew&&$sourceItemsNew->count()) {
|
||||
foreach ($this->requiredFields as $field) {
|
||||
foreach($sourceItemsNew as $item){
|
||||
$cellName = $this->getName().'['.$item->ID.']['.$field.']';
|
||||
$cName = $this->getName().'[new]['.$field.'][]';
|
||||
|
||||
if($fieldObj = $fields->dataFieldByName($cellName)) {
|
||||
if(!trim($fieldObj->Value())){
|
||||
$title = $fieldObj->Title();
|
||||
$errorMessage .= sprintf(
|
||||
_t('TableField.ISREQUIRED', "In %s '%s' is required"),
|
||||
$this->name,
|
||||
$title
|
||||
);
|
||||
$errorMessage .= "<br />";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($errorMessage){
|
||||
$messageType .= "validation";
|
||||
$message .= "<br />".$errorMessage;
|
||||
|
||||
$validator->validationError($this->name, $message, $messageType);
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
public function setRequiredFields($fields) {
|
||||
$this->requiredFields = $fields;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single record in a TableField.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
* @see TableField
|
||||
*/
|
||||
class TableField_Item extends TableListField_Item {
|
||||
|
||||
/**
|
||||
* @var FieldList $fields
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
protected $data;
|
||||
|
||||
protected $fieldTypes;
|
||||
|
||||
protected $isAddRow;
|
||||
|
||||
protected $extraData;
|
||||
|
||||
/**
|
||||
* Each row contains a dataobject with any number of attributes
|
||||
* @param $ID int The ID of the record
|
||||
* @param $form Form A Form object containing all of the fields for this item. The data should be loaded in
|
||||
* @param $fieldTypes array An array of name => fieldtype for use when creating a new field
|
||||
* @param $parent TableListField The parent table for quick reference of names, and id's for storing values.
|
||||
*/
|
||||
public function __construct($item = null, $parent, $form, $fieldTypes, $isAddRow = false) {
|
||||
$this->data = $form;
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->isAddRow = $isAddRow;
|
||||
$this->item = $item;
|
||||
|
||||
parent::__construct(($this->item) ? $this->item : new DataObject(), $parent);
|
||||
|
||||
$this->fields = $this->createFields();
|
||||
}
|
||||
/**
|
||||
* Represents each cell of the table with an attribute.
|
||||
*
|
||||
* @return FieldList
|
||||
*/
|
||||
public function createFields() {
|
||||
// Existing record
|
||||
if($this->item && $this->data) {
|
||||
$form = $this->data;
|
||||
$this->fieldset = $form->Fields();
|
||||
$this->fieldset->removeByName('SecurityID');
|
||||
if($this->fieldset) {
|
||||
$i=0;
|
||||
foreach($this->fieldset as $field) {
|
||||
$origFieldName = $field->getName();
|
||||
|
||||
// set unique fieldname with id
|
||||
$combinedFieldName = $this->parent->getName() . "[" . $this->ID() . "][" . $origFieldName . "]";
|
||||
// ensure to set field to nested array notation
|
||||
// if its an unsaved row, or the "add row" which is present by default
|
||||
if($this->isAddRow || $this->ID() == 'new') $combinedFieldName .= '[]';
|
||||
|
||||
// get value
|
||||
if(strpos($origFieldName,'.') === false) {
|
||||
$value = $field->dataValue();
|
||||
} else {
|
||||
// this supports the syntax fieldName = Relation.RelatedField
|
||||
$fieldNameParts = explode('.', $origFieldName) ;
|
||||
$tmpItem = $this->item;
|
||||
for($j=0;$j<sizeof($fieldNameParts);$j++) {
|
||||
$relationMethod = $fieldNameParts[$j];
|
||||
$idField = $relationMethod . 'ID';
|
||||
if($j == sizeof($fieldNameParts)-1) {
|
||||
$value = $tmpItem->$relationMethod;
|
||||
} else {
|
||||
$tmpItem = $tmpItem->$relationMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$field->Name = $combinedFieldName;
|
||||
$field->setValue($field->dataValue());
|
||||
$field->addExtraClass('col'.$i);
|
||||
$field->setForm($this->data);
|
||||
|
||||
// transformation
|
||||
if(isset($this->parent->transformationConditions[$origFieldName])) {
|
||||
$transformation = $this->parent->transformationConditions[$origFieldName]['transformation'];
|
||||
$rule = str_replace("\$","\$this->item->",
|
||||
$this->parent->transformationConditions[$origFieldName]['rule']);
|
||||
$ruleApplies = null;
|
||||
eval('$ruleApplies = ('.$rule.');');
|
||||
if($ruleApplies) {
|
||||
$field = $field->$transformation();
|
||||
}
|
||||
}
|
||||
|
||||
// formatting
|
||||
$item = $this->item;
|
||||
$value = $field->Value();
|
||||
if(array_key_exists($origFieldName, $this->parent->fieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$origFieldName]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
$field->dontEscape = true;
|
||||
$field->setValue($value);
|
||||
}
|
||||
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
// New record
|
||||
} else {
|
||||
$list = $this->parent->FieldList();
|
||||
$i=0;
|
||||
foreach($list as $fieldName => $fieldTitle) {
|
||||
if(strpos($fieldName, ".")) {
|
||||
$shortFieldName = substr($fieldName, strpos($fieldName, ".")+1, strlen($fieldName));
|
||||
} else {
|
||||
$shortFieldName = $fieldName;
|
||||
}
|
||||
$combinedFieldName = $this->parent->getName() . "[new][" . $shortFieldName . "][]";
|
||||
$fieldType = $this->fieldTypes[$fieldName];
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
$field = clone $fieldType; // we can't use the same instance all over, as we change names
|
||||
$field->Name = $combinedFieldName;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
//echo ("<li>Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."</li>");
|
||||
$field = new $fieldType($combinedFieldName,$fieldTitle);
|
||||
} else {
|
||||
$field = eval("return new " . $fieldType . ";");
|
||||
}
|
||||
$field->addExtraClass('col'.$i);
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
return new FieldList($this->fields);
|
||||
}
|
||||
|
||||
public function Fields($xmlSafe = true) {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
public function ExtraData() {
|
||||
$content = "";
|
||||
$id = ($this->item->ID) ? $this->item->ID : "new";
|
||||
if($this->parent->getExtraData()) {
|
||||
foreach($this->parent->getExtraData() as $fieldName=>$fieldValue) {
|
||||
$name = $this->parent->getName() . "[" . $id . "][" . $fieldName . "]";
|
||||
if($this->isAddRow) $name .= '[]';
|
||||
$field = new HiddenField($name, null, $fieldValue);
|
||||
$content .= $field->FieldHolder() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the flag isAddRow of this item,
|
||||
* to indicate if the item is that blank last row in the table which is not in the database
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function IsAddRow(){
|
||||
return $this->isAddRow;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,1644 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
|
||||
/**
|
||||
* Form field that embeds a list into a form, such as a member list or a file list.
|
||||
*
|
||||
* All get variables are namespaced in the format ctf[MyFieldName][MyParameter] to avoid collisions
|
||||
* when multiple TableListFields are present in a form.
|
||||
*
|
||||
* @deprecated 3.1 Use GridField with GridFieldConfig_RecordEditor
|
||||
*
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class TableListField extends FormField {
|
||||
/**
|
||||
* The {@link DataList} object defining the source data for this view/
|
||||
*/
|
||||
protected $dataList;
|
||||
|
||||
protected $fieldList;
|
||||
|
||||
protected $disableSorting = false;
|
||||
|
||||
/**
|
||||
* @var $fieldListCsv array
|
||||
*/
|
||||
protected $fieldListCsv;
|
||||
|
||||
/**
|
||||
* @var $clickAction
|
||||
*/
|
||||
protected $clickAction;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $IsReadOnly;
|
||||
|
||||
/**
|
||||
* Called method (needs to be retained for AddMode())
|
||||
*/
|
||||
protected $methodName;
|
||||
|
||||
/**
|
||||
* @var $summaryFieldList array Shows a row which summarizes the contents of a column by a predefined
|
||||
* Javascript-function
|
||||
*/
|
||||
protected $summaryFieldList;
|
||||
|
||||
/**
|
||||
* @var $summaryTitle string The title which will be shown in the first column of the summary-row.
|
||||
* Accordingly, the first column can't be used for summarizing.
|
||||
*/
|
||||
protected $summaryTitle;
|
||||
|
||||
/**
|
||||
* @var $template string Template-Overrides
|
||||
*/
|
||||
protected $template = "TableListField";
|
||||
|
||||
/**
|
||||
* @var $itemClass string Class name for each item/row
|
||||
*/
|
||||
public $itemClass = 'TableListField_Item';
|
||||
|
||||
/**
|
||||
* @var bool Do we use checkboxes to mark records, or delete them one by one?
|
||||
*/
|
||||
public $Markable;
|
||||
|
||||
public $MarkableTitle = null;
|
||||
|
||||
/**
|
||||
* @var array See {@link SelectOptions()}
|
||||
*/
|
||||
|
||||
protected $selectOptions = array();
|
||||
|
||||
/**
|
||||
* @var $readOnly boolean Deprecated, please use $permssions instead
|
||||
*/
|
||||
protected $readOnly;
|
||||
|
||||
/**
|
||||
* @var $permissions array Influence output without having to subclass the template.
|
||||
* See $actions for adding your custom actions/permissions.
|
||||
*/
|
||||
protected $permissions = array(
|
||||
//"print",
|
||||
//"export",
|
||||
"delete"
|
||||
);
|
||||
|
||||
/**
|
||||
* @var $actions array Action that can be performed on a single row-entry.
|
||||
* Has to correspond to a method in a TableListField-class (or subclass).
|
||||
* Actions can be disabled through $permissions.
|
||||
* Format (key is used for the methodname and CSS-class):
|
||||
* array(
|
||||
* 'delete' => array(
|
||||
* 'label' => 'Delete',
|
||||
* 'icon' => 'framework/images/delete.gif',
|
||||
* 'icon_disabled' => 'framework/images/delete_disabled.gif',
|
||||
* 'class' => 'deletelink',
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public $actions = array(
|
||||
'delete' => array(
|
||||
'label' => 'Delete',
|
||||
'icon' => 'framework/images/delete.gif',
|
||||
'icon_disabled' => 'framework/images/delete_disabled.gif',
|
||||
'class' => 'deletelink'
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* @var $defaultAction String Action being executed when clicking on table-row (defaults to "show").
|
||||
* Mostly needed in ComplexTableField-subclass.
|
||||
*/
|
||||
public $defaultAction = '';
|
||||
|
||||
/**
|
||||
* @var $customCsvQuery Query for CSV-export (might need different fields or further filtering)
|
||||
*/
|
||||
protected $customCsvQuery;
|
||||
|
||||
/**
|
||||
* Character to seperate exported columns in the CSV file
|
||||
*/
|
||||
protected $csvSeparator = ",";
|
||||
|
||||
/*
|
||||
* Boolean deciding whether to include a header row in the CSV file
|
||||
*/
|
||||
protected $csvHasHeader = true;
|
||||
|
||||
/**
|
||||
* @var array Specify custom escape for the fields.
|
||||
*
|
||||
* <code>
|
||||
* array("\""=>"\"\"","\r"=>"", "\r\n"=>"", "\n"=>"")
|
||||
* </code>
|
||||
*/
|
||||
public $csvFieldEscape = array(
|
||||
"\""=>"\"\"",
|
||||
"\r\n"=>"",
|
||||
"\r"=>"",
|
||||
"\n"=>"",
|
||||
);
|
||||
|
||||
/**
|
||||
* @var boolean Trigger pagination
|
||||
*/
|
||||
protected $showPagination = false;
|
||||
|
||||
/**
|
||||
* @var string Override the {@link Link()} method
|
||||
* for all pagination. Useful to force rendering of the field
|
||||
* in a different context.
|
||||
*/
|
||||
public $paginationBaseLink = null;
|
||||
|
||||
/**
|
||||
* @var int Number of items to show on a single page (needed for pagination)
|
||||
*/
|
||||
protected $pageSize = 10;
|
||||
|
||||
/**
|
||||
* @var array Definitions for highlighting table-rows with a specific class. You can use all column-names
|
||||
* in the result of a query. Use in combination with {@setCustomQuery} to select custom properties and joined
|
||||
* objects.
|
||||
*
|
||||
* Example:
|
||||
* array(
|
||||
* array(
|
||||
* "rule" => '$Flag == "red"',
|
||||
* "class" => "red"
|
||||
* ),
|
||||
* array(
|
||||
* "rule" => '$Flag == "orange"',
|
||||
* "class" => "orange"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public $highlightConditions = array();
|
||||
|
||||
/**
|
||||
* @var array Specify castings with fieldname as the key, and the desired casting as value.
|
||||
* Example: array("MyCustomDate"=>"Date","MyShortText"=>"Text->FirstSentence")
|
||||
*/
|
||||
public $fieldCasting = array();
|
||||
|
||||
/**
|
||||
* @var array Specify custom formatting for fields, e.g. to render a link instead of pure text.
|
||||
* Caution: Make sure to escape special php-characters like in a normal php-statement.
|
||||
* Example: "myFieldName" => '<a href=\"custom-admin/$ID\">$ID</a>'
|
||||
*/
|
||||
public $fieldFormatting = array();
|
||||
|
||||
public $csvFieldFormatting = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $exportButtonLabel = 'Export as CSV';
|
||||
|
||||
/**
|
||||
* @var string $groupByField Used to group by a specific column in the DataObject
|
||||
* and create partial summaries.
|
||||
*/
|
||||
public $groupByField = null;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $extraLinkParams;
|
||||
|
||||
protected $__cachedQuery;
|
||||
|
||||
/**
|
||||
* This is a flag that enables some backward-compatibility helpers.
|
||||
*/
|
||||
private $getDataListFromForm;
|
||||
|
||||
/**
|
||||
* @param $name string The fieldname
|
||||
* @param $sourceClass string The source class of this field
|
||||
* @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
|
||||
* @param $sourceFilter string The filter field you wish to limit the objects by (eg. parentID)
|
||||
* @param $sourceSort string
|
||||
* @param $sourceJoin string
|
||||
*/
|
||||
public function __construct($name, $sourceClass = null, $fieldList = null, $sourceFilter = null,
|
||||
$sourceSort = null, $sourceJoin = null) {
|
||||
|
||||
if($sourceClass) {
|
||||
// You can optionally pass a list
|
||||
if($sourceClass instanceof SS_List) {
|
||||
$this->dataList = $sourceClass;
|
||||
|
||||
} else {
|
||||
$this->dataList = DataObject::get($sourceClass)->where($sourceFilter)->sort($sourceSort);
|
||||
if($sourceJoin) $this->dataList = $this->dataList->join($sourceJoin);
|
||||
// Grab it from the form relation, if available.
|
||||
$this->getDataListFromForm = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fieldList = ($fieldList) ? $fieldList : singleton($this->sourceClass())->summaryFields();
|
||||
|
||||
$this->readOnly = false;
|
||||
|
||||
parent::__construct($name);
|
||||
}
|
||||
|
||||
public function index() {
|
||||
return $this->FieldHolder();
|
||||
}
|
||||
|
||||
static $url_handlers = array(
|
||||
'item/$ID' => 'handleItem',
|
||||
'$Action' => '$Action',
|
||||
);
|
||||
|
||||
public function sourceClass() {
|
||||
$list = $this->getDataList();
|
||||
if(method_exists($list, 'dataClass')) return $list->dataClass();
|
||||
// Failover for SS_List
|
||||
else return get_class($list->First());
|
||||
}
|
||||
|
||||
public function handleItem($request) {
|
||||
return new TableListField_ItemRequest($this, $request->param('ID'));
|
||||
}
|
||||
|
||||
public function FieldHolder($properties = array()) {
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/prototype/prototype.js');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/behaviour/behaviour.js');
|
||||
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/javascript/lang');
|
||||
Requirements::javascript(FRAMEWORK_DIR . '/javascript/TableListField.js');
|
||||
Requirements::css(FRAMEWORK_DIR . '/css/TableListField.css');
|
||||
|
||||
if($this->clickAction) {
|
||||
$id = $this->id();
|
||||
Requirements::customScript(<<<JS
|
||||
Behaviour.register({
|
||||
'#$id tr' : {
|
||||
onclick : function() {
|
||||
$this->clickAction
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
JS
|
||||
);}
|
||||
|
||||
$obj = $properties ? $this->customise($properties) : $this;
|
||||
return $obj->renderWith($this->template);
|
||||
}
|
||||
|
||||
public function Headings() {
|
||||
$headings = array();
|
||||
foreach($this->fieldList as $fieldName => $fieldTitle) {
|
||||
$isSorted = (isset($_REQUEST['ctf'][$this->getName()]['sort'])
|
||||
&& $fieldName == $_REQUEST['ctf'][$this->getName()]['sort']);
|
||||
|
||||
// we can't allow sorting with partial summaries (groupByField)
|
||||
$isSortable = ($this->form && $this->isFieldSortable($fieldName) && !$this->groupByField);
|
||||
|
||||
// sorting links (only if we have a form to refresh with)
|
||||
if($this->form) {
|
||||
$sortLink = $this->Link();
|
||||
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][sort]", $fieldName, $sortLink,'&');
|
||||
|
||||
// Apply sort direction to the current sort field
|
||||
if(!empty($_REQUEST['ctf'][$this->getName()]['sort'])
|
||||
&& ($_REQUEST['ctf'][$this->getName()]['sort'] == $fieldName)) {
|
||||
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
|
||||
$dir = $_REQUEST['ctf'][$this->getName()]['dir'];
|
||||
} else {
|
||||
$dir = null;
|
||||
}
|
||||
|
||||
$dir = trim(strtolower($dir));
|
||||
$newDir = ($dir == 'desc') ? null : 'desc';
|
||||
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][dir]", Convert::raw2xml($newDir),
|
||||
$sortLink,'&');
|
||||
}
|
||||
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['search'])
|
||||
&& is_array($_REQUEST['ctf'][$this->getName()]['search'])) {
|
||||
|
||||
foreach($_REQUEST['ctf'][$this->getName()]['search'] as $parameter => $value) {
|
||||
$XML_search = Convert::raw2xml($value);
|
||||
$sortLink = HTTP::setGetVar("ctf[{$this->getName()}][search][$parameter]", $XML_search,
|
||||
$sortLink,'&');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sortLink = '#';
|
||||
}
|
||||
|
||||
$headings[] = new ArrayData(array(
|
||||
"Name" => $fieldName,
|
||||
"Title" => ($this->sourceClass())
|
||||
? singleton($this->sourceClass())->fieldLabel($fieldTitle)
|
||||
: $fieldTitle,
|
||||
"IsSortable" => $isSortable,
|
||||
"SortLink" => $sortLink,
|
||||
"SortBy" => $isSorted,
|
||||
"SortDirection" => (isset($_REQUEST['ctf'][$this->getName()]['dir']))
|
||||
? $_REQUEST['ctf'][$this->getName()]['dir']
|
||||
: null
|
||||
));
|
||||
}
|
||||
return new ArrayList($headings);
|
||||
}
|
||||
|
||||
public function disableSorting($to = true) {
|
||||
$this->disableSorting = $to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a field is "sortable".
|
||||
* If the field is generated by a custom getter, we can't sort on it
|
||||
* without generating all objects first (which would be a huge performance impact).
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return bool
|
||||
*/
|
||||
public function isFieldSortable($fieldName) {
|
||||
if($this->disableSorting) return false;
|
||||
$list = $this->getDataList();
|
||||
if(method_exists($list,'canSortBy')) return $list->canSortBy($fieldName);
|
||||
else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy function to get number of actions originally generated in
|
||||
* TableListField_Item.
|
||||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Actions() {
|
||||
$allowedActions = new ArrayList();
|
||||
foreach($this->actions as $actionName => $actionSettings) {
|
||||
if($this->Can($actionName)) {
|
||||
$allowedActions->push(new ViewableData());
|
||||
}
|
||||
}
|
||||
|
||||
return $allowedActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a custom query to compute sourceItems. This is the preferred way to using
|
||||
* {@setSourceItems}, because we can still paginate.
|
||||
* Please use this only as a fallback for really complex queries (e.g. involving HAVING and GROUPBY).
|
||||
*
|
||||
* @param $query DataList
|
||||
*/
|
||||
public function setCustomQuery(DataList $dataList) {
|
||||
$this->dataList = $dataList;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCustomCsvQuery(DataList $dataList) {
|
||||
$this->customCsvQuery = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCustomSourceItems(SS_List $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
|
||||
if($items instanceof SS_List) {
|
||||
$this->dataList = $items;
|
||||
} else {
|
||||
user_error('TableList::setCustomSourceItems() should be passed a SS_List', E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items, with sort & limit applied
|
||||
*/
|
||||
public function sourceItems() {
|
||||
// get items (this may actually be a SS_List)
|
||||
$items = clone $this->getDataList();
|
||||
|
||||
// TODO: Sorting could be implemented on regular SS_Lists.
|
||||
if(method_exists($items,'canSortBy') && isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$sort = $_REQUEST['ctf'][$this->getName()]['sort'];
|
||||
// TODO: sort direction
|
||||
if($items->canSortBy($sort)) $items = $items->sort($sort);
|
||||
}
|
||||
|
||||
// Determine pagination limit, offset
|
||||
// To disable pagination, set $this->showPagination to false.
|
||||
if($this->showPagination && $this->pageSize) {
|
||||
$SQL_limit = (int)$this->pageSize;
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
&& is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
$SQL_start = intval($_REQUEST['ctf'][$this->getName()]['start']);
|
||||
} else {
|
||||
$SQL_start = "0";
|
||||
}
|
||||
} else {
|
||||
$SQL_start = 0;
|
||||
}
|
||||
|
||||
$items = $items->limit($SQL_limit, $SQL_start);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a SS_List of TableListField_Item objects, suitable for display in the template.
|
||||
*/
|
||||
public function Items() {
|
||||
$fieldItems = new ArrayList();
|
||||
if($items = $this->sourceItems()) foreach($items as $item) {
|
||||
if($item) $fieldItems->push(new $this->itemClass($item, $this));
|
||||
}
|
||||
return $fieldItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DataList for this field.
|
||||
*/
|
||||
public function getDataList() {
|
||||
// If we weren't passed in a DataList to begin with, try and get the datalist from the form
|
||||
if($this->form && $this->getDataListFromForm) {
|
||||
$this->getDataListFromForm = false;
|
||||
$relation = $this->name;
|
||||
if($record = $this->form->getRecord()) {
|
||||
if($record->hasMethod($relation)) $this->dataList = $record->$relation();
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->dataList) {
|
||||
user_error(get_class($this). ' is missing a DataList', E_USER_ERROR);
|
||||
}
|
||||
|
||||
return $this->dataList;
|
||||
}
|
||||
|
||||
public function getCsvDataList() {
|
||||
if($this->customCsvQuery) return $this->customCsvQuery;
|
||||
else return $this->getDataList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getDataList() instead.
|
||||
*/
|
||||
public function getQuery() {
|
||||
Deprecation::notice('3.0', 'Use getDataList() instead.');
|
||||
$list = $this->getDataList();
|
||||
if(method_exists($list,'dataQuery')) {
|
||||
return $this->getDataList()->dataQuery()->query();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getCsvDataList() instead.
|
||||
*/
|
||||
public function getCsvQuery() {
|
||||
Deprecation::notice('3.0', 'Use getCsvDataList() instead.');
|
||||
$list = $this->getCsvDataList();
|
||||
if(method_exists($list,'dataQuery')) {
|
||||
return $list->dataQuery()->query();
|
||||
}
|
||||
}
|
||||
|
||||
public function FieldList() {
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this table to load content into a subform via ajax
|
||||
*/
|
||||
public function setClick_AjaxLoad($urlBase, $formID) {
|
||||
$this->clickAction = "this.ajaxRequest('" . addslashes($urlBase) . "', '" . addslashes($formID) . "')";
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure this table to open a popup window
|
||||
*/
|
||||
public function setClick_PopupLoad($urlBase) {
|
||||
$this->clickAction = "var w = window.open(baseHref()+'$urlBase'+this.id.replace(/.*-(\d*)$/,'$1'), 'popup');"
|
||||
. " w.focus();";
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function performReadonlyTransformation() {
|
||||
$clone = clone $this;
|
||||
$clone->setShowPagination(false);
|
||||
|
||||
// Only include the show action if it was in the original CTF.
|
||||
$clone->setPermissions(in_array('show', $this->permissions) ? array('show') : array());
|
||||
|
||||
$clone->addExtraClass( 'readonly' );
|
||||
$clone->setReadonly(true);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* CRUD
|
||||
* #################################
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
public function delete($request) {
|
||||
// Protect against CSRF on destructive action
|
||||
$token = $this->getForm()->getSecurityToken();
|
||||
if(!$token->checkRequest($request)) return $this->httpError('400');
|
||||
|
||||
if($this->Can('delete') !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->methodName = "delete";
|
||||
|
||||
$childId = Convert::raw2sql($_REQUEST['ctf']['childID']);
|
||||
|
||||
if (is_numeric($childId)) {
|
||||
$this->getDataList()->removeById($childId);
|
||||
}
|
||||
|
||||
// TODO return status in JSON etc.
|
||||
//return $this->renderWith($this->template);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Summary-Row
|
||||
* #################################
|
||||
*/
|
||||
|
||||
/**
|
||||
* Can utilize some built-in summary-functions, with optional casting.
|
||||
* Currently supported:
|
||||
* - sum
|
||||
* - avg
|
||||
*
|
||||
* @param $summaryTitle string
|
||||
* @param $summaryFields array
|
||||
* Simple Format: array("MyFieldName"=>"sum")
|
||||
* With Casting: array("MyFieldname"=>array("sum","Currency->Nice"))
|
||||
*/
|
||||
public function addSummary($summaryTitle, $summaryFieldList) {
|
||||
$this->summaryTitle = $summaryTitle;
|
||||
$this->summaryFieldList = $summaryFieldList;
|
||||
}
|
||||
|
||||
public function removeSummary() {
|
||||
$this->summaryTitle = null;
|
||||
$this->summaryFields = null;
|
||||
}
|
||||
|
||||
public function HasSummary() {
|
||||
return (isset($this->summaryFieldList));
|
||||
}
|
||||
|
||||
public function SummaryTitle() {
|
||||
return $this->summaryTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SS_List $items Only used to pass grouped sourceItems for creating
|
||||
* partial summaries.
|
||||
*/
|
||||
public function SummaryFields($items = null) {
|
||||
if(!isset($this->summaryFieldList)) {
|
||||
return false;
|
||||
}
|
||||
$summaryFields = array();
|
||||
$fieldListWithoutFirst = $this->fieldList;
|
||||
if(!empty($this->summaryTitle)) {
|
||||
array_shift($fieldListWithoutFirst);
|
||||
}
|
||||
foreach($fieldListWithoutFirst as $fieldName => $fieldTitle) {
|
||||
|
||||
if(in_array($fieldName, array_keys($this->summaryFieldList))) {
|
||||
if(is_array($this->summaryFieldList[$fieldName])) {
|
||||
$summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName][0]}";
|
||||
$casting = $this->summaryFieldList[$fieldName][1];
|
||||
} else {
|
||||
$summaryFunction = "colFunction_{$this->summaryFieldList[$fieldName]}";
|
||||
$casting = null;
|
||||
}
|
||||
|
||||
// fall back to integrated sourceitems if not passed
|
||||
if(!$items) $items = $this->sourceItems();
|
||||
|
||||
$summaryValue = ($items) ? $this->$summaryFunction($items->column($fieldName)) : null;
|
||||
|
||||
// Optional casting, Format: array('MyFieldName'=>array('sum','Currency->Nice'))
|
||||
if(isset($casting)) {
|
||||
$summaryValue = $this->getCastedValue($summaryValue, $casting);
|
||||
}
|
||||
} else {
|
||||
$summaryValue = null;
|
||||
$function = null;
|
||||
}
|
||||
|
||||
$summaryFields[] = new ArrayData(array(
|
||||
'Function' => $function,
|
||||
'SummaryValue' => $summaryValue,
|
||||
'Name' => DBField::create_field('Varchar', $fieldName),
|
||||
'Title' => DBField::create_field('Varchar', $fieldTitle),
|
||||
));
|
||||
}
|
||||
return new ArrayList($summaryFields);
|
||||
}
|
||||
|
||||
public function HasGroupedItems() {
|
||||
return ($this->groupByField);
|
||||
}
|
||||
|
||||
public function GroupedItems() {
|
||||
if(!$this->groupByField) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$items = $this->sourceItems();
|
||||
if(!$items || !$items->Count()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$groupedItems = $items->groupBy($this->groupByField);
|
||||
$groupedArrItems = new ArrayList();
|
||||
foreach($groupedItems as $key => $group) {
|
||||
$fieldItems = new ArrayList();
|
||||
foreach($group as $item) {
|
||||
if($item) $fieldItems->push(new $this->itemClass($item, $this));
|
||||
}
|
||||
$groupedArrItems->push(new ArrayData(array(
|
||||
'Items' => $fieldItems,
|
||||
'SummaryFields' => $this->SummaryFields($group)
|
||||
)));
|
||||
}
|
||||
|
||||
return $groupedArrItems;
|
||||
}
|
||||
|
||||
public function colFunction_sum($values) {
|
||||
return array_sum($values);
|
||||
}
|
||||
|
||||
public function colFunction_avg($values) {
|
||||
return array_sum($values)/count($values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Permissions
|
||||
* #################################
|
||||
*/
|
||||
|
||||
/**
|
||||
* Template accessor for Permissions.
|
||||
* See {@link TableListField_Item->Can()} for object-specific
|
||||
* permissions.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function Can($mode) {
|
||||
if($mode == 'add' && $this->isReadonly()) {
|
||||
return false;
|
||||
} else if($mode == 'delete' && $this->isReadonly()) {
|
||||
return false;
|
||||
} else if($mode == 'edit' && $this->isReadonly()) {
|
||||
return false;
|
||||
} else {
|
||||
return (in_array($mode, $this->permissions));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function setPermissions($arr) {
|
||||
$this->permissions = $arr;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions() {
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Pagination
|
||||
* #################################
|
||||
*/
|
||||
public function setShowPagination($bool) {
|
||||
$this->showPagination = (bool)$bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function ShowPagination() {
|
||||
if($this->showPagination && !empty($this->summaryFieldList)) {
|
||||
user_error("You can't combine pagination and summaries - please disable one of them.", E_USER_ERROR);
|
||||
}
|
||||
return $this->showPagination;
|
||||
}
|
||||
|
||||
public function setPageSize($pageSize) {
|
||||
$this->pageSize = $pageSize;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function PageSize() {
|
||||
return $this->pageSize;
|
||||
}
|
||||
|
||||
public function ListStart() {
|
||||
return $_REQUEST['ctf'][$this->getName()]['start'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array
|
||||
* @deprecated Put the query string onto your form's link instead :-)
|
||||
*/
|
||||
public function setExtraLinkParams($params){
|
||||
Deprecation::notice('2.4', 'Put the query string onto your FormAction instead().');
|
||||
$this->extraLinkParams = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getExtraLinkParams(){
|
||||
return $this->extraLinkParams;
|
||||
}
|
||||
|
||||
public function FirstLink() {
|
||||
$start = 0;
|
||||
|
||||
if(!isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
|| !is_numeric($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
|| $_REQUEST['ctf'][$this->getName()]['start'] == 0) {
|
||||
|
||||
return null;
|
||||
}
|
||||
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
|
||||
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
|
||||
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
|
||||
|
||||
// preserve sort options
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
|
||||
// direction
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
|
||||
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function PrevLink() {
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
$currentStart = $_REQUEST['ctf'][$this->getName()]['start'];
|
||||
} else {
|
||||
$currentStart = 0;
|
||||
}
|
||||
|
||||
if($currentStart == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if($_REQUEST['ctf'][$this->getName()]['start'] - $this->pageSize < 0) {
|
||||
$start = 0;
|
||||
} else {
|
||||
$start = $_REQUEST['ctf'][$this->getName()]['start'] - $this->pageSize;
|
||||
}
|
||||
|
||||
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
|
||||
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
|
||||
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
|
||||
|
||||
// preserve sort options
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
|
||||
// direction
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
|
||||
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
|
||||
public function NextLink() {
|
||||
$currentStart = isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
? $_REQUEST['ctf'][$this->getName()]['start']
|
||||
: 0;
|
||||
$start = ($currentStart + $this->pageSize < $this->TotalCount())
|
||||
? $currentStart + $this->pageSize
|
||||
: $this->TotalCount() % $this->pageSize > 0;
|
||||
if($currentStart >= $start-1) {
|
||||
return null;
|
||||
}
|
||||
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
|
||||
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
|
||||
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
|
||||
|
||||
// preserve sort options
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
|
||||
// direction
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
|
||||
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function LastLink() {
|
||||
$pageSize = ($this->TotalCount() % $this->pageSize > 0)
|
||||
? $this->TotalCount() % $this->pageSize
|
||||
: $this->pageSize;
|
||||
$start = $this->TotalCount() - $pageSize;
|
||||
// Check if there is only one page, or if we are on last page
|
||||
if($this->TotalCount() <= $pageSize || (isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
&& $_REQUEST['ctf'][$this->getName()]['start'] >= $start)) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$baseLink = ($this->paginationBaseLink) ? $this->paginationBaseLink : $this->Link();
|
||||
$link = Controller::join_links($baseLink, "?ctf[{$this->getName()}][start]={$start}");
|
||||
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
|
||||
|
||||
// preserve sort options
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$link .= "&ctf[{$this->getName()}][sort]=" . $_REQUEST['ctf'][$this->getName()]['sort'];
|
||||
// direction
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['dir'])) {
|
||||
$link .= "&ctf[{$this->getName()}][dir]=" . $_REQUEST['ctf'][$this->getName()]['dir'];
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
public function FirstItem() {
|
||||
if ($this->TotalCount() < 1) return 0;
|
||||
return isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
? $_REQUEST['ctf'][$this->getName()]['start'] + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
public function LastItem() {
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
$pageStep = min($this->pageSize, $this->TotalCount() - $_REQUEST['ctf'][$this->getName()]['start']);
|
||||
return $_REQUEST['ctf'][$this->getName()]['start'] + $pageStep;
|
||||
} else {
|
||||
return min($this->pageSize, $this->TotalCount());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
private $_cache_TotalCount;
|
||||
|
||||
/**
|
||||
* Return the total number of items in the source DataList
|
||||
*/
|
||||
public function TotalCount() {
|
||||
if($this->_cache_TotalCount === null) {
|
||||
$this->_cache_TotalCount = $this->getDataList()->Count();
|
||||
}
|
||||
return $this->_cache_TotalCount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Search
|
||||
* #################################
|
||||
*
|
||||
* @todo Not fully implemented at the moment
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compile all request-parameters for search and pagination
|
||||
* (except the actual list-positions) as a query-string.
|
||||
*
|
||||
* @return String URL-parameters
|
||||
*/
|
||||
public function filterString() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* CSV Export
|
||||
* #################################
|
||||
*/
|
||||
public function setFieldListCsv($fields) {
|
||||
$this->fieldListCsv = $fields;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSV separator character. Defaults to ,
|
||||
*/
|
||||
public function setCsvSeparator($csvSeparator) {
|
||||
$this->csvSeparator = $csvSeparator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSV separator character. Defaults to ,
|
||||
*/
|
||||
public function getCsvSeparator() {
|
||||
return $this->csvSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the header row from the CSV export
|
||||
*/
|
||||
public function removeCsvHeader() {
|
||||
$this->csvHasHeader = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a given set of comma-separated IDs (from a previous search-query, stored in a HiddenField).
|
||||
* Uses {$csv_columns} if present, and falls back to {$result_columns}.
|
||||
* We move the most filedata generation code to the function {@link generateExportFileData()} so that a child class
|
||||
* could reuse the filedata generation code while overwrite export function.
|
||||
*
|
||||
* @todo Make relation-syntax available (at the moment you'll have to use custom sql)
|
||||
*/
|
||||
public function export() {
|
||||
$now = date("d-m-Y-H-i");
|
||||
$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)){
|
||||
return SS_HTTPRequest::send_file($fileData, $fileName, 'text/csv');
|
||||
} else {
|
||||
user_error("No records found", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function generateExportFileData(&$numColumns, &$numRows) {
|
||||
$separator = $this->csvSeparator;
|
||||
$csvColumns = ($this->fieldListCsv) ? $this->fieldListCsv : $this->fieldList;
|
||||
$fileData = '';
|
||||
$columnData = array();
|
||||
$fieldItems = new ArrayList();
|
||||
|
||||
if($this->csvHasHeader) {
|
||||
$fileData .= "\"" . implode("\"{$separator}\"", array_values($csvColumns)) . "\"";
|
||||
$fileData .= "\n";
|
||||
}
|
||||
|
||||
if(isset($this->customSourceItems)) {
|
||||
$items = $this->customSourceItems;
|
||||
} else {
|
||||
$items = $this->getCsvDataList();
|
||||
}
|
||||
|
||||
// temporary override to adjust TableListField_Item behaviour
|
||||
$this->setFieldFormatting(array());
|
||||
$this->fieldList = $csvColumns;
|
||||
|
||||
if($items) {
|
||||
foreach($items as $item) {
|
||||
if(is_array($item)) {
|
||||
$className = isset($item['RecordClassName']) ? $item['RecordClassName'] : $item['ClassName'];
|
||||
$item = new $className($item);
|
||||
}
|
||||
$fieldItem = new $this->itemClass($item, $this);
|
||||
|
||||
$fields = $fieldItem->Fields(false);
|
||||
$columnData = array();
|
||||
if($fields) foreach($fields as $field) {
|
||||
$value = $field->Value;
|
||||
|
||||
// TODO This should be replaced with casting
|
||||
if(array_key_exists($field->Name, $this->csvFieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->csvFieldFormatting[$field->Name]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
}
|
||||
|
||||
$value = str_replace(array("\r", "\n"), "\n", $value);
|
||||
$tmpColumnData = '"' . str_replace('"', '\"', $value) . '"';
|
||||
$columnData[] = $tmpColumnData;
|
||||
}
|
||||
$fileData .= implode($separator, $columnData);
|
||||
$fileData .= "\n";
|
||||
|
||||
$item->destroy();
|
||||
unset($item);
|
||||
unset($fieldItem);
|
||||
}
|
||||
|
||||
$numColumns = count($columnData);
|
||||
$numRows = $fieldItems->count();
|
||||
return $fileData;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to instanciate this button manually as a normal button has no means of adding inline onclick-behaviour.
|
||||
*/
|
||||
public function ExportLink() {
|
||||
$exportLink = Controller::join_links($this->Link(), 'export');
|
||||
|
||||
if($this->extraLinkParams) $exportLink .= "?" . http_build_query($this->extraLinkParams);
|
||||
return $exportLink;
|
||||
}
|
||||
|
||||
public function printall() {
|
||||
Requirements::clear();
|
||||
if(defined('CMS_DIR')) {
|
||||
Requirements::css(CMS_DIR . '/css/typography.css');
|
||||
Requirements::css(CMS_DIR . '/css/cms_right.css');
|
||||
}
|
||||
|
||||
$this->cachedSourceItems = null;
|
||||
$oldShowPagination = $this->showPagination;
|
||||
$this->showPagination = false;
|
||||
|
||||
increase_time_limit_to();
|
||||
$this->Print = true;
|
||||
|
||||
$result = $this->renderWith(array($this->template . '_printable', 'TableListField_printable'));
|
||||
|
||||
$this->showPagination = $oldShowPagination;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function PrintLink() {
|
||||
$link = Controller::join_links($this->Link(), 'printall');
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['sort'])) {
|
||||
$link = HTTP::setGetVar("ctf[{$this->getName()}][sort]",
|
||||
Convert::raw2xml($_REQUEST['ctf'][$this->getName()]['sort']), $link);
|
||||
}
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* #################################
|
||||
* Utilty
|
||||
* #################################
|
||||
*/
|
||||
public function Utility() {
|
||||
$links = new ArrayList();
|
||||
if($this->can('export')) {
|
||||
$links->push(new ArrayData(array(
|
||||
'Title' => _t('TableListField.CSVEXPORT', 'Export to CSV'),
|
||||
'Link' => $this->ExportLink()
|
||||
)));
|
||||
}
|
||||
if($this->can('print')) {
|
||||
$links->push(new ArrayData(array(
|
||||
'Title' => _t('TableListField.PRINT', 'Print'),
|
||||
'Link' => $this->PrintLink()
|
||||
)));
|
||||
}
|
||||
return $links;
|
||||
|
||||
}
|
||||
|
||||
public function setFieldCasting($casting) {
|
||||
$this->fieldCasting = $casting;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFieldFormatting($formatting) {
|
||||
$this->fieldFormatting = $formatting;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCSVFieldFormatting($formatting) {
|
||||
$this->csvFieldFormatting = $formatting;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the field list
|
||||
*/
|
||||
public function setFieldList($fieldList) {
|
||||
$this->fieldList = $fieldList;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
public function Name() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function Title() {
|
||||
// adding translating functionality
|
||||
// this is a bit complicated, because this parameter is passed to this class
|
||||
// and should come here translated already
|
||||
// adding this to TODO probably add a method to the classes
|
||||
// to return they're translated string
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName();
|
||||
}
|
||||
|
||||
public function NameSingular() {
|
||||
// same as Title()
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return $this->sourceClass() ? singleton($this->sourceClass())->singular_name() : $this->getName();
|
||||
}
|
||||
|
||||
public function NamePlural() {
|
||||
// same as Title()
|
||||
// added by ruibarreiros @ 27/11/2007
|
||||
return $this->sourceClass() ? singleton($this->sourceClass())->plural_name() : $this->getName();
|
||||
}
|
||||
|
||||
public function setTemplate($template) {
|
||||
$this->template = $template;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function CurrentLink() {
|
||||
$link = $this->Link();
|
||||
|
||||
if(isset($_REQUEST['ctf'][$this->getName()]['start'])
|
||||
&& is_numeric($_REQUEST['ctf'][$this->getName()]['start'])) {
|
||||
|
||||
$start = ($_REQUEST['ctf'][$this->getName()]['start'] < 0)
|
||||
? 0
|
||||
: $_REQUEST['ctf'][$this->getName()]['start'];
|
||||
$link = Controller::join_links($link, "?ctf[{$this->getName()}][start]={$start}");
|
||||
}
|
||||
|
||||
if($this->extraLinkParams) $link .= "&" . http_build_query($this->extraLinkParams);
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded to automatically add security token.
|
||||
*
|
||||
* @param String $action
|
||||
* @return String
|
||||
*/
|
||||
public function Link($action = null) {
|
||||
$form = $this->getForm();
|
||||
if($form) {
|
||||
$token = $form->getSecurityToken();
|
||||
$parentUrlParts = parse_url(parent::Link($action));
|
||||
$queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null;
|
||||
// Ensure that URL actions not routed through Form->httpSubmission() are protected against CSRF attacks.
|
||||
if($token->isEnabled()) $queryPart = $token->addtoUrl($queryPart);
|
||||
return Controller::join_links($parentUrlParts['path'], $queryPart);
|
||||
} else {
|
||||
// allow for instanciation of this FormField outside of a controller/form
|
||||
// context (e.g. for unit tests)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function BaseLink() {
|
||||
user_error("TableListField::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
|
||||
return $this->Link();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine permissions for a scaffolded
|
||||
* TableListField (or subclasses) - currently used in {@link ModelAdmin} and
|
||||
* {@link DataObject->scaffoldFormFields()}.
|
||||
*
|
||||
* Returns true for each permission that doesn't have an explicit getter.
|
||||
*
|
||||
* @todo Temporary method, implement directly in FormField subclasses with object-level permissions.
|
||||
*
|
||||
* @param string $class
|
||||
* @param numeric $id
|
||||
* @return array
|
||||
*/
|
||||
public static function permissions_for_object($class, $id = null) {
|
||||
$permissions = array();
|
||||
$obj = ($id) ? DataObject::get_by_id($class, $id) : singleton($class);
|
||||
|
||||
if(!$obj->hasMethod('canView') || $obj->canView()) $permissions[] = 'show';
|
||||
if(!$obj->hasMethod('canEdit') || $obj->canEdit()) $permissions[] = 'edit';
|
||||
if(!$obj->hasMethod('canDelete') || $obj->canDelete()) $permissions[] = 'delete';
|
||||
if(!$obj->hasMethod('canCreate') || $obj->canCreate()) $permissions[] = 'add';
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*
|
||||
*/
|
||||
public function getCastedValue($value, $castingDefinition) {
|
||||
if(is_array($castingDefinition)) {
|
||||
$castingParams = $castingDefinition;
|
||||
array_shift($castingParams);
|
||||
$castingDefinition = array_shift($castingDefinition);
|
||||
} else {
|
||||
$castingParams = array();
|
||||
}
|
||||
if(strpos($castingDefinition,'->') === false) {
|
||||
$castingFieldType = $castingDefinition;
|
||||
$castingField = DBField::create_field($castingFieldType, $value);
|
||||
$value = call_user_func_array(array($castingField,'XML'),$castingParams);
|
||||
} else {
|
||||
$fieldTypeParts = explode('->', $castingDefinition);
|
||||
$castingFieldType = $fieldTypeParts[0];
|
||||
$castingMethod = $fieldTypeParts[1];
|
||||
$castingField = DBField::create_field($castingFieldType, $value);
|
||||
$value = call_user_func_array(array($castingField,$castingMethod),$castingParams);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function setHighlightConditions($conditions) {
|
||||
$this->highlightConditions = $conditions;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link SelectOptions()} for introduction.
|
||||
*
|
||||
* @param $options array Options to add, key being a unique identifier of the action,
|
||||
* and value a title for the rendered link element (can contain HTML).
|
||||
* The keys for 'all' and 'none' have special behaviour associated
|
||||
* through TableListField.js JavaScript.
|
||||
* For any other key, the JavaScript automatically checks all checkboxes contained in
|
||||
* <td> elements with a matching classname.
|
||||
*/
|
||||
public function addSelectOptions($options){
|
||||
foreach($options as $k => $title)
|
||||
$this->selectOptions[$k] = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one all more table's {@link $selectOptions}
|
||||
*
|
||||
* @param $optionsNames array
|
||||
*/
|
||||
public function removeSelectOptions($names){
|
||||
foreach($names as $name){
|
||||
unset($this->selectOptions[trim($name)]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table's {@link $selectOptions}.
|
||||
* Used to toggle checkboxes for each table row through button elements.
|
||||
*
|
||||
* Requires {@link Markable()} to return TRUE.
|
||||
* This is only functional with JavaScript enabled.
|
||||
*
|
||||
* @return SS_List of ArrayData objects
|
||||
*/
|
||||
public function SelectOptions(){
|
||||
if(!$this->selectOptions) return;
|
||||
|
||||
$selectOptionsSet = new ArrayList();
|
||||
foreach($this->selectOptions as $k => $v) {
|
||||
$selectOptionsSet->push(new ArrayData(array(
|
||||
'Key' => $k,
|
||||
'Value' => $v
|
||||
)));
|
||||
}
|
||||
return $selectOptionsSet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single record in a TableListField.
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
* @see TableListField
|
||||
*/
|
||||
class TableListField_Item extends ViewableData {
|
||||
|
||||
/**
|
||||
* @var DataObject The underlying data record,
|
||||
* usually an element of {@link TableListField->sourceItems()}.
|
||||
*/
|
||||
protected $item;
|
||||
|
||||
/**
|
||||
* @var TableListField
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
public function __construct($item, $parent) {
|
||||
$this->failover = $this->item = $item;
|
||||
$this->parent = $parent;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function ID() {
|
||||
return $this->item->ID;
|
||||
}
|
||||
|
||||
public function Parent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
public function Fields($xmlSafe = true) {
|
||||
$list = $this->parent->FieldList();
|
||||
foreach($list as $fieldName => $fieldTitle) {
|
||||
$value = "";
|
||||
|
||||
// This supports simple FieldName syntax
|
||||
if(strpos($fieldName,'.') === false) {
|
||||
$value = ($this->item->XML_val($fieldName) && $xmlSafe)
|
||||
? $this->item->XML_val($fieldName)
|
||||
: $this->item->RAW_val($fieldName);
|
||||
// This support the syntax fieldName = Relation.RelatedField
|
||||
} else {
|
||||
$fieldNameParts = explode('.', $fieldName) ;
|
||||
$tmpItem = $this->item;
|
||||
for($j=0;$j<sizeof($fieldNameParts);$j++) {
|
||||
$relationMethod = $fieldNameParts[$j];
|
||||
$idField = $relationMethod . 'ID';
|
||||
if($j == sizeof($fieldNameParts)-1) {
|
||||
if($tmpItem) $value = $tmpItem->$relationMethod;
|
||||
} else {
|
||||
if($tmpItem) $tmpItem = $tmpItem->$relationMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// casting
|
||||
if(array_key_exists($fieldName, $this->parent->fieldCasting)) {
|
||||
$value = $this->parent->getCastedValue($value, $this->parent->fieldCasting[$fieldName]);
|
||||
} elseif(is_object($value) && method_exists($value, 'Nice')) {
|
||||
$value = $value->Nice();
|
||||
}
|
||||
|
||||
// formatting
|
||||
$item = $this->item;
|
||||
if(array_key_exists($fieldName, $this->parent->fieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$fieldName]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
}
|
||||
|
||||
//escape
|
||||
if($escape = $this->parent->fieldEscape){
|
||||
foreach($escape as $search => $replace){
|
||||
$value = str_replace($search, $replace, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$fields[] = new ArrayData(array(
|
||||
"Name" => $fieldName,
|
||||
"Title" => $fieldTitle,
|
||||
"Value" => $value,
|
||||
"CsvSeparator" => $this->parent->getCsvSeparator(),
|
||||
));
|
||||
}
|
||||
return new ArrayList($fields);
|
||||
}
|
||||
|
||||
public function Markable() {
|
||||
return $this->parent->Markable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks global permissions for field in {@link TableListField->Can()}.
|
||||
* If they are allowed, it checks for object permissions by assuming
|
||||
* a method with "can" + $mode parameter naming, e.g. canDelete().
|
||||
*
|
||||
* @param string $mode See {@link TableListField::$permissions} array.
|
||||
* @return boolean
|
||||
*/
|
||||
public function Can($mode) {
|
||||
$canMethod = "can" . ucfirst($mode);
|
||||
if(!$this->parent->Can($mode)) {
|
||||
// check global settings for the field instance
|
||||
return false;
|
||||
} elseif($this->item->hasMethod($canMethod)) {
|
||||
// if global allows, check object specific permissions (e.g. canDelete())
|
||||
return $this->item->$canMethod();
|
||||
} else {
|
||||
// otherwise global allowed this action, so return TRUE
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function Link($action = null) {
|
||||
$form = $this->parent->getForm();
|
||||
if($form) {
|
||||
$token = $form->getSecurityToken();
|
||||
$parentUrlParts = parse_url($this->parent->Link());
|
||||
$queryPart = (isset($parentUrlParts['query'])) ? '?' . $parentUrlParts['query'] : null;
|
||||
// Ensure that URL actions not routed through Form->httpSubmission() are protected against CSRF attacks.
|
||||
if($token->isEnabled()) $queryPart = $token->addtoUrl($queryPart);
|
||||
return Controller::join_links($parentUrlParts['path'], 'item', $this->item->ID, $action, $queryPart);
|
||||
} else {
|
||||
// allow for instanciation of this FormField outside of a controller/form
|
||||
// context (e.g. for unit tests)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all row-based actions not disallowed through permissions.
|
||||
* See TableListField->Action for a similiar dummy-function to work
|
||||
* around template-inheritance issues.
|
||||
*
|
||||
* @return SS_List
|
||||
*/
|
||||
public function Actions() {
|
||||
$allowedActions = new ArrayList();
|
||||
foreach($this->parent->actions as $actionName => $actionSettings) {
|
||||
if($this->parent->Can($actionName)) {
|
||||
$allowedActions->push(new ArrayData(array(
|
||||
'Name' => $actionName,
|
||||
'Link' => $this->{ucfirst($actionName).'Link'}(),
|
||||
'Icon' => $actionSettings['icon'],
|
||||
'IconDisabled' => $actionSettings['icon_disabled'],
|
||||
'Label' => $actionSettings['label'],
|
||||
'Class' => $actionSettings['class'],
|
||||
'Default' => ($actionName == $this->parent->defaultAction),
|
||||
'IsAllowed' => $this->Can($actionName),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return $allowedActions;
|
||||
}
|
||||
|
||||
public function BaseLink() {
|
||||
user_error("TableListField_Item::BaseLink() deprecated, use Link() instead", E_USER_NOTICE);
|
||||
return $this->Link();
|
||||
}
|
||||
|
||||
public function DeleteLink() {
|
||||
return Controller::join_links($this->Link(), "delete");
|
||||
}
|
||||
|
||||
public function MarkingCheckbox() {
|
||||
$name = $this->parent->getName() . '[]';
|
||||
|
||||
if($this->parent->isReadonly())
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\""
|
||||
. " disabled=\"disabled\" />";
|
||||
else
|
||||
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\" />";
|
||||
}
|
||||
|
||||
/**
|
||||
* According to {@link TableListField->selectOptions}, each record will check if the options' key on the object is
|
||||
* true, if it is true, add the key as a class to the record
|
||||
*
|
||||
* @return string Value for a 'class' HTML attribute.
|
||||
*/
|
||||
public function SelectOptionClasses(){
|
||||
$tagArray = array('markingcheckbox');
|
||||
$options = $this->parent->SelectOptions();
|
||||
if($options && $options->exists()){
|
||||
foreach($options as $option){
|
||||
if($option->Key !== 'all' && $option->Key !== 'none'){
|
||||
if($this->{$option->Key}) {
|
||||
$tagArray[] = $option->Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return implode(" ",$tagArray);
|
||||
}
|
||||
|
||||
public function HighlightClasses() {
|
||||
$classes = array();
|
||||
foreach($this->parent->highlightConditions as $condition) {
|
||||
$rule = str_replace("\$","\$this->item->", $condition['rule']);
|
||||
$ruleApplies = null;
|
||||
eval('$ruleApplies = ('.$rule.');');
|
||||
if($ruleApplies) {
|
||||
if(isset($condition['exclusive']) && $condition['exclusive']) {
|
||||
return $condition['class'];
|
||||
} else {
|
||||
$classes[] = $condition['class'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (count($classes) > 0) ? " " . implode(" ", $classes) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy: Please use permissions instead
|
||||
*/
|
||||
public function isReadonly() {
|
||||
return $this->parent->Can('delete');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @package forms
|
||||
* @subpackage fields-relational
|
||||
*/
|
||||
class TableListField_ItemRequest extends RequestHandler {
|
||||
protected $ctf;
|
||||
protected $itemID;
|
||||
protected $methodName;
|
||||
|
||||
static $url_handlers = array(
|
||||
'$Action!' => '$Action',
|
||||
'' => 'index',
|
||||
);
|
||||
|
||||
public function Link() {
|
||||
return Controller::join_links($this->ctf->Link(), 'item/' . $this->itemID);
|
||||
}
|
||||
|
||||
public function __construct($ctf, $itemID) {
|
||||
$this->ctf = $ctf;
|
||||
$this->itemID = $itemID;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
$this->dataObj()->delete();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Return the data object being manipulated
|
||||
*/
|
||||
public function dataObj() {
|
||||
// used to discover fields if requested and for population of field
|
||||
if(is_numeric($this->itemID)) {
|
||||
// we have to use the basedataclass, otherwise we might exclude other subclasses
|
||||
return $this->ctf->getDataList()->byId($this->itemID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TableListField
|
||||
*/
|
||||
public function getParentController() {
|
||||
return $this->ctf;
|
||||
}
|
||||
}
|
||||
|
@ -1,156 +0,0 @@
|
||||
// Shortcut-function (until we update to Prototye v1.5)
|
||||
if(typeof $$ != "Function") $$ = document.getElementsBySelector;
|
||||
|
||||
GB_OpenerObj = {};
|
||||
GB_RefreshLink = "";
|
||||
|
||||
ComplexTableField = Class.create();
|
||||
ComplexTableField.prototype = {
|
||||
|
||||
// These are defaults used if setPopupSize encounters errors
|
||||
defaultPopupWidth: 560,
|
||||
defaultPopupHeight: 390,
|
||||
|
||||
initialize: function() {
|
||||
var rules = {};
|
||||
rules['#'+this.id+' table.data a.popuplink'] = {onclick: this.openPopup.bind(this)};
|
||||
|
||||
// Assume that the delete link uses the deleteRecord method
|
||||
rules['#'+this.id+' table.data a.deletelink'] = {onclick: this.deleteRecord.bind(this)};
|
||||
|
||||
// invoke row action-link based on default-action set in classname
|
||||
var defaultAction = this.getDefaultAction();
|
||||
|
||||
if(defaultAction) {
|
||||
rules['#'+this.id+' table.data tbody td'] = {
|
||||
onclick: function(e) {
|
||||
var elt = Event.element(e);
|
||||
// Check the tag, as otherwise this
|
||||
// function can take over checkbox
|
||||
// click actions etc. See ticket #4737
|
||||
if (elt.tagName != 'TD' && elt.tagName != 'TR') {
|
||||
return;
|
||||
}
|
||||
|
||||
var link = $$('.'+defaultAction, Event.element(e).parentNode)[0].href;
|
||||
this.openPopup(null, link);
|
||||
return false;
|
||||
}.bind(this)
|
||||
};
|
||||
}
|
||||
Behaviour.register('ComplexTableField_'+this.id,rules);
|
||||
|
||||
this.setPopupSize();
|
||||
|
||||
// HACK If already in a popup, we can't allow add (doesn't save existing relation correctly)
|
||||
if(window != top) $$('#'+this.id+' table.data a.addlink').each(function(el) {Element.hide(el);});
|
||||
},
|
||||
|
||||
setPopupSize: function() {
|
||||
try {
|
||||
this.popupHeight = parseInt(document.getElementById(this.id + '_PopupHeight').value);
|
||||
this.popupWidth = parseInt(document.getElementById(this.id + '_PopupWidth').value);
|
||||
} catch (ex) {
|
||||
this.popupHeight = this.defaultPopupHeight;
|
||||
this.popupWidth = this.defaultPopupWidth;
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultAction: function() {
|
||||
// try to get link class from <td class="action default"><a href="...
|
||||
var links = $$('#'+this.id+' table.data tbody .default a');
|
||||
// fall back to first given link
|
||||
if(!links || !links[0]) links = $$('#'+this.id+' table.data tbody .action a');
|
||||
return (links && links[0]) ? $A(Element.classNames(links[0])).last() : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param href, table Optional dom object (use for external triggering without an event)
|
||||
*/
|
||||
openPopup: function(e, _popupLink, _table) {
|
||||
// If already in a popup, simply open the link instead
|
||||
// of opening a nested lightwindow
|
||||
if(window != top) return true;
|
||||
|
||||
this.setPopupSize();
|
||||
|
||||
var el,type;
|
||||
var popupLink = "";
|
||||
if(_popupLink) {
|
||||
popupLink = _popupLink;
|
||||
table = _table;
|
||||
} else {
|
||||
// if clicked item is an input-element, don't trigger popup
|
||||
var el = Event.element(e);
|
||||
var input = Event.findElement(e,"input");
|
||||
var tr = Event.findElement(e, "tr");
|
||||
|
||||
// stop on non-found lines
|
||||
if(tr && Element.hasClassName(tr, 'notfound')) {
|
||||
Event.stop(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// normal behaviour for input elements
|
||||
if(el.nodeName == "INPUT" || input.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
var table = Event.findElement(e,"table");
|
||||
if(Event.element(e).nodeName == "IMG") {
|
||||
link = Event.findElement(e,"a");
|
||||
popupLink = link.href + (link.href.match(/\?/) ? "&ajax=1" : "?ajax=1");
|
||||
} else {
|
||||
el = Event.findElement(e,"tr");
|
||||
var link = $$("a",el)[0];
|
||||
popupLink = link.href;
|
||||
}
|
||||
} catch(err) {
|
||||
// no link found
|
||||
Event.stop(e);
|
||||
return false;
|
||||
}
|
||||
// no link found
|
||||
if(!link || popupLink.length == 0) {
|
||||
Event.stop(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.GB_Caption) {
|
||||
var title = this.GB_Caption;
|
||||
} else {
|
||||
// Getting the title from the URL is pretty ugly, but it works for now
|
||||
type = popupLink.match(/[0-9]+\/([^\/?&]*)([?&]|$)/);
|
||||
var title = (type && type[1]) ? type[1].ucfirst() : "";
|
||||
}
|
||||
|
||||
// reset internal greybox callbacks, they are not properly unregistered
|
||||
// and fire multiple times on each subsequent popup close action otherwise
|
||||
if(GB_ONLY_ONE) GB_ONLY_ONE.callback_fn = [];
|
||||
|
||||
GB_show(
|
||||
title,
|
||||
popupLink,
|
||||
this.popupHeight,
|
||||
this.popupWidth,
|
||||
this.refresh.bind(this)
|
||||
);
|
||||
|
||||
if(e) {
|
||||
Event.stop(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ComplexTableField.applyTo('div.ComplexTableField');
|
||||
|
||||
/**
|
||||
* Get first letter as uppercase
|
||||
*/
|
||||
String.prototype.ucfirst = function () {
|
||||
var firstLetter = this.substr(0,1).toUpperCase()
|
||||
return this.substr(0,1).toUpperCase() + this.substr(1,this.length);
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
ComplexTableFieldPopupForm = Class.create();
|
||||
ComplexTableFieldPopupForm.prototype = {
|
||||
|
||||
initialize: function() {
|
||||
var rules = {};
|
||||
|
||||
Behaviour.register(rules);
|
||||
}
|
||||
}
|
||||
ComplexTableFieldPopupForm.applyTo('#ComplexTableField_Popup_DetailForm');
|
||||
ComplexTableFieldPopupForm.applyTo('#ComplexTableField_Popup_AddForm');
|
@ -1,137 +0,0 @@
|
||||
HasManyFileField = Class.create();
|
||||
HasManyFileField.applyTo('div.hasmanyfile');
|
||||
HasManyFileField.prototype = {
|
||||
initialize: function() {
|
||||
HasManyFileFieldAddButton.applyToChildren(this, 'a.addFile');
|
||||
HasManyFileFieldRemoveButton.applyToChildren(this, 'a.removeFile');
|
||||
HasManyFileFieldUploadButton.applyToChildren(this, 'a.uploadFile');
|
||||
this.tree = document.getElementsByClassName('TreeDropdownField', this)[0];
|
||||
this.list = this.getElementsByTagName('ul')[0];
|
||||
this.uploadFolderID = this.getElementsByTagName('input')[0].value;
|
||||
this.uploadMessage = document.getElementsByClassName('uploadMessage')[0];
|
||||
}
|
||||
}
|
||||
|
||||
HasManyFileFieldAddButton = Class.create();
|
||||
HasManyFileFieldAddButton.prototype = {
|
||||
onclick: function() {
|
||||
tree = this.parentNode.parentNode.tree;
|
||||
list = this.parentNode.parentNode.list;
|
||||
fieldid = this.parentNode.parentNode.id;
|
||||
fileid = tree.getElementsByTagName('input')[0].value;
|
||||
name = tree.getElementsByTagName('span')[0].innerHTML;
|
||||
|
||||
input = document.createElement('input');
|
||||
input.className = 'hidden';
|
||||
input.type = 'hidden';
|
||||
input.name = fieldid + '[]';
|
||||
input.value = fileid;
|
||||
|
||||
text = document.createTextNode(name);
|
||||
link = document.createElement('a');
|
||||
link.appendChild(text);
|
||||
|
||||
removelink = document.createElement('a');
|
||||
removelink.className = 'removeFile';
|
||||
removelink.innerHTML = 'Remove file';
|
||||
|
||||
li = document.createElement('li');
|
||||
li.appendChild(input);
|
||||
li.appendChild(link);
|
||||
li.appendChild(removelink);
|
||||
list.appendChild(li);
|
||||
|
||||
HasManyFileFieldRemoveButton.applyTo(removelink);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HasManyFileFieldRemoveButton = Class.create();
|
||||
HasManyFileFieldRemoveButton.prototype = {
|
||||
onclick: function() {
|
||||
li = this.parentNode;
|
||||
list = this.parentNode.parentNode;
|
||||
list.removeChild(li);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HasManyFileFieldUploadButton = Class.create();
|
||||
HasManyFileFieldUploadButton.prototype = {
|
||||
initialize: function() {
|
||||
this.upload = new Upload({
|
||||
fileUploadLimit : '6',
|
||||
securityID : document.getElementById('SecurityID').value,
|
||||
beginUploadOnQueue : true,
|
||||
fileQueued : this.uploadFileQueuedCallback.bind(this),
|
||||
fileComplete : this.uploadFileCompleteCallback.bind(this),
|
||||
queueComplete : this.uploadQueueCompleteCallback.bind(this)
|
||||
});
|
||||
|
||||
this.upload.setFolderID(this.parentNode.parentNode.uploadFolderID);
|
||||
},
|
||||
|
||||
buildUI: function() {
|
||||
|
||||
},
|
||||
|
||||
onclick: function(event) {
|
||||
Event.stop(event);
|
||||
this.upload.browse();
|
||||
},
|
||||
|
||||
uploadFileQueuedCallback: function(file,queueLength) {
|
||||
var message = ss.i18n.sprintf(
|
||||
ss.i18n._t('HASMANYFILEFIELD.UPLOADING', 'Uploading... %s'),
|
||||
this.upload.getFilesToUpload()
|
||||
);
|
||||
this.parentNode.parentNode.uploadMessage.innerHTML = message;
|
||||
},
|
||||
|
||||
uploadFileCompleteCallback: function(file,serverData) {
|
||||
var message = ss.i18n.sprintf(
|
||||
ss.i18n._t('HASMANYFILEFIELD.UPLOADING', 'Uploading... %s'),
|
||||
this.upload.getFilesUploaded() + "/" + this.upload.getFilesToUpload()
|
||||
);
|
||||
this.parentNode.parentNode.uploadMessage.innerHTML = message;
|
||||
idregex = /\/\* IDs: ([0-9,]+) \*\//;
|
||||
ids = serverData.match(idregex);
|
||||
fileid = ids[1];
|
||||
|
||||
nameregex = /\/\* Names: ([^\s]+) \*\//;
|
||||
names = serverData.match(nameregex);
|
||||
name = names[1];
|
||||
|
||||
fieldid = this.parentNode.parentNode.id;
|
||||
list = this.parentNode.parentNode.list;
|
||||
|
||||
input = document.createElement('input');
|
||||
input.className = 'hidden';
|
||||
input.type = 'hidden';
|
||||
input.name = fieldid + '[]';
|
||||
input.value = fileid;
|
||||
|
||||
text = document.createTextNode(name);
|
||||
link = document.createElement('a');
|
||||
link.appendChild(text);
|
||||
|
||||
removelink = document.createElement('a');
|
||||
removelink.className = 'removeFile';
|
||||
removelink.innerHTML = 'Remove file';
|
||||
|
||||
li = document.createElement('li');
|
||||
li.appendChild(input);
|
||||
li.appendChild(link);
|
||||
li.appendChild(removelink);
|
||||
list.appendChild(li);
|
||||
|
||||
HasManyFileFieldRemoveButton.applyTo(removelink);
|
||||
},
|
||||
|
||||
uploadQueueCompleteCallback: function() {
|
||||
this.parentNode.parentNode.uploadMessage.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
@ -1,117 +0,0 @@
|
||||
|
||||
|
||||
Event.observe( window, 'load', function() {
|
||||
if(document.getElementById('sitetree')){
|
||||
if(typeof document.getElementById('sitetree').observeMethod != 'undefined') {
|
||||
document.getElementById('sitetree').observeMethod( 'NodeClicked' , function() {
|
||||
checkedListNameArray = null;
|
||||
checkedArray = null;
|
||||
} );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
RelationComplexTableField = Class.create();
|
||||
RelationComplexTableField.prototype = {
|
||||
initialize: function() {
|
||||
var checkedListNameArray = null;
|
||||
var checkedListEndName = 'CheckedList';
|
||||
var checkedListField = 'selected';
|
||||
var checkedArray = null;
|
||||
|
||||
// 1) Find The Hidden Field Where The IDs Will Be Stored
|
||||
|
||||
var checkedList = document.getElementById( this.id + '_' + checkedListEndName );
|
||||
|
||||
// 2) Initialize The Array Or Update The Hidden Input Field And The HTML Table
|
||||
|
||||
var checkedListName = checkedList.getAttribute( 'name' );
|
||||
//if( checkedListNameArray == null ) {
|
||||
checkedListNameArray = [];
|
||||
checkedListNameArray.push( checkedListName );
|
||||
checkedArray = [];
|
||||
if( checkedList.getAttribute( 'value' ) )
|
||||
checkedArray.push( checkedList.getAttribute( 'value' ).split( ',' ) );
|
||||
//}
|
||||
/*
|
||||
else if( checkedListNameArray.indexOf( checkedListName ) < 0 ) {
|
||||
checkedListNameArray.push( checkedListName );
|
||||
if( checkedList.getAttribute( 'value' ) )
|
||||
checkedArray[ checkedListNameArray.length - 1 ] = checkedList.getAttribute( 'value' ).split( ',' );
|
||||
}
|
||||
else {
|
||||
|
||||
var index = checkedListNameArray.indexOf( checkedListName );
|
||||
|
||||
// a) Update The Hidden Input Field
|
||||
|
||||
checkedList.setAttribute( 'value', checkedArray[ index ] );
|
||||
|
||||
// b) Update The HTML Table
|
||||
|
||||
markingInputs = document.getElementsByName( checkedListName.substring( 0, checkedListName.indexOf( '[' ) ) + '[]' );
|
||||
|
||||
for( var i = 0; i < markingInputs.length; i++ ) {
|
||||
markingInput = markingInputs[ i ];
|
||||
if( checkedArray[ index ] && checkedArray[ index ].indexOf( markingInput.getAttribute( 'value' ) ) > -1 ) {
|
||||
markingInput.setAttribute( 'checked', 'checked' );}
|
||||
else
|
||||
markingInput.removeAttribute( 'checked' );
|
||||
}
|
||||
} */
|
||||
|
||||
// 3) Create The Rules
|
||||
|
||||
var rules = {};
|
||||
|
||||
rules[ '#' + this.id + ' table.data tbody td.markingcheckbox input' ] = {
|
||||
onclick : function() {
|
||||
|
||||
// 1) Find The Hidden Field Where The IDs Will Be Stored
|
||||
|
||||
var checkedListName = this.getAttribute( 'name' );
|
||||
checkedListName = checkedListName.substring( 0, checkedListName.length - 1 ) + checkedListField + ']';
|
||||
var inputs = document.getElementsByTagName( 'input' );
|
||||
for( var i = 0; i < inputs.length; i++ ) {
|
||||
var checkedList = inputs[ i ];
|
||||
if( checkedList.getAttribute( 'name' ) == checkedListName )
|
||||
break;
|
||||
}
|
||||
var index = checkedListNameArray.indexOf( checkedListName );
|
||||
|
||||
// 2) Update The Array
|
||||
|
||||
if( checkedArray[ index ] && checkedArray[ index ].indexOf( this.getAttribute( 'value' ) ) > -1 ) {
|
||||
index2 = checkedArray[ index ].indexOf( this.getAttribute( 'value' ) );
|
||||
var previousCheckedArray = checkedArray[ index ];
|
||||
checkedArray[ index ] = [];
|
||||
for( var i = 0; i < previousCheckedArray.length; i++ ) {
|
||||
if( i != index2 )
|
||||
checkedArray[ index ].push( previousCheckedArray[ i ] );
|
||||
}
|
||||
if( this.getAttribute( 'type' ) == 'radio' )
|
||||
this.checked = false;
|
||||
}
|
||||
else if( checkedArray[ index ] ) {
|
||||
if( this.getAttribute( 'type' ) == 'radio' )
|
||||
checkedArray[ index ] = [];
|
||||
checkedArray[ index ].push( this.getAttribute( 'value' ) );
|
||||
}
|
||||
else {
|
||||
checkedArray[ index ] = [];
|
||||
checkedArray[ index ].push( this.getAttribute( 'value' ) );
|
||||
}
|
||||
|
||||
// 3) Update The Hidden Input Field
|
||||
|
||||
checkedList.setAttribute( 'value', checkedArray[ index ] );
|
||||
}
|
||||
};
|
||||
|
||||
Behaviour.register( rules );
|
||||
}
|
||||
}
|
||||
|
||||
RelationComplexTableField.applyTo('#Form_EditForm div.HasOneComplexTableField');
|
||||
RelationComplexTableField.applyTo('#Form_EditForm div.HasManyComplexTableField');
|
||||
RelationComplexTableField.applyTo('#Form_EditForm div.ManyManyComplexTableField');
|
@ -1,191 +0,0 @@
|
||||
/**
|
||||
* Javascript for TableField. allows the deletion of records via
|
||||
* AJAX, and the addition of rows via javasript.
|
||||
*
|
||||
* TODO relies on include-order at the moment to override actions :/
|
||||
*/
|
||||
TableField = Class.create();
|
||||
TableField.prototype = {
|
||||
|
||||
newRowID: 1,
|
||||
|
||||
/**
|
||||
* Applies behaviour to the delete button for deleting objects via ajax.
|
||||
*/
|
||||
initialize: function() {
|
||||
var rules = {};
|
||||
|
||||
rules['#'+this.id+' table.data a.deletelink'] = {
|
||||
onclick: this.deleteRecord.bind(this)
|
||||
};
|
||||
|
||||
rules['#'+this.id+' table.data a.addrow'] = {
|
||||
onclick: this.addRow.bind(this)
|
||||
};
|
||||
|
||||
Behaviour.register('TableField_'+this.id,rules);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given dataobject record via an ajax request. If the record doesn't have any
|
||||
* information in it, it just removes it from the form.
|
||||
* to tablefield->Delete()
|
||||
* @param {Object} e
|
||||
*/
|
||||
deleteRecord: function(e) {
|
||||
var img = Event.element(e);
|
||||
var link = Event.findElement(e,"a");
|
||||
var row = Event.findElement(e,"tr");
|
||||
var params = link.getAttribute("href").toQueryParams();
|
||||
var isEmpty = true;
|
||||
var recordID = row.getRecordId();
|
||||
var self = this;
|
||||
|
||||
// Check to see if there is a dataobject to delete first, otherwise remove the row.
|
||||
// or: Check if a childID is set (not present on new items)
|
||||
if(
|
||||
(this.hasNoValues(row,"input") && this.hasNoValues(row,"select") && this.hasNoValues(row,"textarea"))
|
||||
|| params["childID"] <= 0 || (recordID <= 0 || recordID == false)
|
||||
){
|
||||
if( row.parentNode.getElementsByTagName('tr').length > 1 ) {
|
||||
jQuery(row).fadeOut();
|
||||
} else {
|
||||
// clear all fields in the row
|
||||
var fields = row.getElementsByTagName('input');
|
||||
if( fields )
|
||||
for( var i = 0; i < fields.length; i++ ) {
|
||||
fields[i].value = '';
|
||||
}
|
||||
}
|
||||
Event.stop(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in framework
|
||||
var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?'));
|
||||
if(confirmed){
|
||||
img.setAttribute("src",'framework/images/network-save.gif'); // TODO doesn't work
|
||||
jQuery.ajax({
|
||||
'url': link.getAttribute("href"),
|
||||
'method': 'post',
|
||||
'data': {ajax: 1, 'SecurityID': document.getElementById('SecurityID') ? document.getElementById('SecurityID').value : null},
|
||||
'success': function(response){
|
||||
jQuery(row).fadeOut('fast', function() {
|
||||
// remove row from DOM
|
||||
this.element.parentNode.removeChild(obj.element);
|
||||
// recalculate summary if needed (assumes that TableListField.js is present)
|
||||
// TODO Proper inheritance
|
||||
if(self._summarise) self._summarise();
|
||||
// custom callback
|
||||
if(self.callback_deleteRecord) self.callback_deleteRecord(e);
|
||||
});
|
||||
},
|
||||
'error': ajaxErrorHandler
|
||||
});
|
||||
}
|
||||
Event.stop(e);
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {Object} element
|
||||
* @param {Object} tagName
|
||||
*/
|
||||
hasNoValues: function(element,tagName){
|
||||
elements = element.getElementsByTagName(tagName);
|
||||
|
||||
if(elements.length >= 1){
|
||||
var isEmpty = true;
|
||||
for(var i = 0; i < elements.length;i++){
|
||||
if(elements[i].type != "hidden"){
|
||||
if(elements[i].value != null && elements[i].value != ""){
|
||||
isEmpty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isEmpty;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Appends a new row to the DOM.
|
||||
*
|
||||
* @param {Object} tableid
|
||||
*/
|
||||
addRow: function (e){
|
||||
|
||||
var table = Event.findElement(e,"table");
|
||||
if(table){
|
||||
// Clone the last TR
|
||||
var tbody = table.tBodies[0];
|
||||
var numRows = tbody.rows.length;
|
||||
var newRow = tbody.rows[0].cloneNode(true);
|
||||
|
||||
// Get the input elements in this new row
|
||||
var inputs = newRow.getElementsByTagName('input');
|
||||
// For every input, set it's value to blank if it is not hidden
|
||||
for(var i = 0; i < inputs.length; i++) {
|
||||
if(inputs[i].type != 'hidden') {
|
||||
inputs[i].value = ""
|
||||
};
|
||||
}
|
||||
|
||||
this.newRowID++;
|
||||
|
||||
if(newRow.id != "new"){
|
||||
this.resetNames(newRow);
|
||||
}
|
||||
|
||||
// Make sure all inputs have unique IDs
|
||||
for(var i = 0; i < inputs.length; i++) {
|
||||
inputs[i].id += "-" + this.newRowID;
|
||||
}
|
||||
|
||||
// Change the ID to a unique one
|
||||
newRow.id = "New_" + this.newRowID;
|
||||
|
||||
// Append the new row to the DOM
|
||||
table.tBodies[0].appendChild(newRow);
|
||||
Behaviour.apply(table);
|
||||
}
|
||||
Event.stop(e);
|
||||
},
|
||||
|
||||
/**
|
||||
* resets the names for all elements inside a row.
|
||||
* @param {Object} row
|
||||
*/
|
||||
resetNames: function(row){
|
||||
|
||||
// Support for addressing the ID's appropriately.
|
||||
for(i = 0; i < row.cells.length;i++){
|
||||
for(b=0; b < row.cells[i].childNodes.length;b++){
|
||||
inputElement = row.cells[i].childNodes[b];
|
||||
if(inputElement.type != 'hidden') inputElement.value = "";
|
||||
if(inputElement.name != null){
|
||||
if(inputElement.name.substr(inputElement.name.length - 2,inputElement.name.length) != "[]"){
|
||||
inputElement.name =
|
||||
inputElement.name.substr(0,inputElement.name.indexOf('[')+1) + "new" +
|
||||
inputElement.name.substr(inputElement.name.indexOf(']'),inputElement.name.length) + "[]";
|
||||
}else{
|
||||
inputElement.name =
|
||||
inputElement.name.substr(0,inputElement.name.indexOf('[')+1) + "new" +
|
||||
inputElement.name.substr(inputElement.name.indexOf(']'),inputElement.name.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TableField.applyTo('div.TableField');
|
||||
if(typeof ajaxErrorHandler == 'undefined'){
|
||||
ajaxErrorHandler = function(response) {
|
||||
alert(response.responseText);
|
||||
}
|
||||
}
|
@ -1,374 +0,0 @@
|
||||
// Shortcut-function (until we update to Prototye v1.5)
|
||||
if(typeof $$ != "Function") $$ = document.getElementsBySelector;
|
||||
|
||||
TableListField = Class.create();
|
||||
TableListField.prototype = {
|
||||
|
||||
errorMessage: "Error talking to server",
|
||||
|
||||
initialize: function() {
|
||||
var rules = {};
|
||||
|
||||
rules['#'+this.id+' table.data a.deletelink'] = {
|
||||
onclick: this.deleteRecord.bind(this)
|
||||
};
|
||||
|
||||
rules['#'+this.id+' th a'] = {
|
||||
onclick: this.refresh.bind(this)
|
||||
};
|
||||
|
||||
rules['#'+this.id+' th'] = {
|
||||
initialize: function() {
|
||||
var sortLinks = $$('span.sortLinkHidden a', this);
|
||||
if(sortLinks) sortLinks[0].style.visibility = 'hidden';
|
||||
},
|
||||
onmouseover: function(e) {
|
||||
var sortLinks = $$('span.sortLinkHidden a', this);
|
||||
if(sortLinks) sortLinks[0].style.visibility = 'visible';
|
||||
},
|
||||
onmouseout: function(e) {
|
||||
var sortLinks = $$('span.sortLinkHidden a', this);
|
||||
if(sortLinks) sortLinks[0].style.visibility = 'hidden';
|
||||
}
|
||||
};
|
||||
|
||||
rules['#'+this.id+' div.PageControls a'] = {onclick: this.refresh.bind(this)};
|
||||
|
||||
rules['#'+this.id+' table.data tr td.markingcheckbox'] = {
|
||||
onclick : function(e) {
|
||||
// do nothing for clicks in marking box cells (e.g. if checkbox is missed)
|
||||
}
|
||||
};
|
||||
|
||||
// rules for selection options on click event
|
||||
rules['#'+this.id+' .selectOptions a'] = {
|
||||
onclick: this.markRecords.bind(this)
|
||||
};
|
||||
|
||||
// initialize summary (if needed)
|
||||
// TODO Breaks with nested divs
|
||||
var summaryCols = $$('tfoot tr.summary td', this);
|
||||
this._summaryDefs = [];
|
||||
|
||||
//should check summaryCols.length, because summaryCols will always be an array, though its length could be 0.
|
||||
if(summaryCols && summaryCols.length) {
|
||||
rules['#'+this.id+' table.data tbody input'] = {
|
||||
onchange: function(e) {
|
||||
if (!e) e = window.event; // stupid IE
|
||||
// workaround for wrong scope with bind(this) and applyTo()
|
||||
var root = Event.findElement(e,'div');
|
||||
// TODO Fix slow $$()-calls and re-enable clientside summaries
|
||||
//root._summarise();
|
||||
}
|
||||
};
|
||||
rules['#'+this.id+' table.data tbody select'] = {
|
||||
onchange: function(e) {
|
||||
if (!e) e = window.event; // stupid IE
|
||||
// workaround for wrong scope with bind(this) and applyTo()
|
||||
var root = Event.findElement(e,'div');
|
||||
// TODO Fix slow $$()-calls and re-enable clientside summaries
|
||||
//root._summarise();
|
||||
}.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
Behaviour.register('TableListField_'+this.id,rules);
|
||||
|
||||
/*
|
||||
if(summaryCols.length) {
|
||||
this._getSummaryDefs(summaryCols);
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes the given dataobject record via an ajax request
|
||||
* to complextablefield->Delete()
|
||||
* @param {Object} e
|
||||
*/
|
||||
deleteRecord: function(e) {
|
||||
var img = Event.element(e);
|
||||
var link = Event.findElement(e,"a");
|
||||
var row = Event.findElement(e,"tr");
|
||||
var self = this;
|
||||
|
||||
// TODO ajaxErrorHandler and loading-image are dependent on cms, but formfield is in framework
|
||||
var confirmed = confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?'));
|
||||
if(confirmed)
|
||||
{
|
||||
img.setAttribute("src",'framework/images/network-save.gif'); // TODO doesn't work
|
||||
jQuery.ajax({
|
||||
'url': link.getAttribute("href"),
|
||||
'method': 'post',
|
||||
'data': {forceajax: 1, SecurityID: jQuery('input[name=SecurityID]').val()},
|
||||
'success': function(){
|
||||
jQuery(row).remove();
|
||||
// recalculate summary if needed (assumes that TableListField.js is present)
|
||||
// TODO Proper inheritance
|
||||
if(self._summarise) self._summarise();
|
||||
// custom callback
|
||||
if(self.callback_deleteRecord) self.callback_deleteRecord(e);
|
||||
},
|
||||
'error': this.ajaxErrorHandler
|
||||
});
|
||||
}
|
||||
Event.stop(e);
|
||||
},
|
||||
|
||||
removeById: function(id) {
|
||||
var el = jQuery('#record-' + this.id + '-' + id)[0];
|
||||
if(el) el.parentNode.removeChild(el);
|
||||
this._summarise();
|
||||
},
|
||||
|
||||
/**
|
||||
* according to the clicked element in "Select bar", mark records that have same class as the element.
|
||||
*/
|
||||
markRecords: function(e){
|
||||
var el = Event.element(e);
|
||||
if(el.nodeName != "a") el = Event.findElement(e,"a");
|
||||
|
||||
if(el.rel == "all"){
|
||||
this.markAll();
|
||||
}else if(el.rel == 'none') {
|
||||
this.unmarkAll();
|
||||
}else{
|
||||
this.unmarkAll();
|
||||
var records = jQuery('#' + this.id + ' td.' + el.rel + ' input.checkbox');
|
||||
var i=0;
|
||||
for(i; i<records.length; i++){
|
||||
records[i].checked = 'checked';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* mark all record in current view of the table
|
||||
*/
|
||||
markAll: function(e){
|
||||
var records = $$('#'+this.id+' td.markingcheckbox input.checkbox');
|
||||
var i=0;
|
||||
for(i; i<records.length; i++){
|
||||
records[i].checked = 'checked';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* unmark all records in current view of the table
|
||||
*/
|
||||
unmarkAll: function(e){
|
||||
var records = $$('#'+this.id+' td.markingcheckbox input.checkbox');
|
||||
var i=0;
|
||||
for(i; i<records.length; i++){
|
||||
records[i].checked = '';
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(e) {
|
||||
var self = this;
|
||||
|
||||
if(e) {
|
||||
var el = Event.element(e);
|
||||
if(el.nodeName != "a") el = Event.findElement(e,"a");
|
||||
} else {
|
||||
var el = jQuery('#' + this.id)[0];
|
||||
}
|
||||
|
||||
if(el.getAttribute('href')) {
|
||||
jQuery.ajax({
|
||||
'url': el.getAttribute('href'),
|
||||
'data': {'update': 1},
|
||||
'success': function(response) {
|
||||
jQuery('#' + self.id).replaceWith(response)
|
||||
// reapply behaviour and reattach methods to TF container node
|
||||
// e.g. <div class="TableListField">
|
||||
Behaviour.apply(jQuery('#' + self.id)[0], true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(e) Event.stop(e);
|
||||
return false;
|
||||
},
|
||||
|
||||
ajaxErrorHandler: function(response) {
|
||||
if(typeof(window.ajaxErrorHandler) == 'function') {
|
||||
window.ajaxErrorHandler();
|
||||
} else {
|
||||
alert(this.errorMessage);
|
||||
}
|
||||
},
|
||||
|
||||
_getSummaryDefs: function(summaryCols) {
|
||||
summaryCols.each(function(col, pos) {
|
||||
if( col ) {
|
||||
var func = this._getSummaryFunction(col.className);
|
||||
this._summaryDefs[pos] = {col: col, pos: pos, func: func};
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this._summarise();
|
||||
},
|
||||
|
||||
_summarise: function() {
|
||||
var rows = $$('tbody tr', this);
|
||||
if(!rows) return false;
|
||||
|
||||
var columnData = [];
|
||||
// prepare the array (gets js-errors otherwise)
|
||||
var cols = $$('td', rows[0]);
|
||||
for(colPos=0; colPos<cols.length; colPos++) {
|
||||
columnData[colPos] = [];
|
||||
}
|
||||
|
||||
for(rowPos=0; rowPos<rows.length; rowPos++) {
|
||||
// avoid wrong calculations for nested lists
|
||||
if(Element.hasClassName(rows[rowPos], "subitem")) continue;
|
||||
|
||||
var cols = $$('td', rows[rowPos]);
|
||||
var colPos = 0;
|
||||
for(colPos; colPos<cols.length; colPos++) {
|
||||
//if(!columnData[colPos]) columnData[colPos] = [];
|
||||
if(this._summaryDefs[colPos] && this._summaryDefs[colPos].func) {
|
||||
columnData[colPos][rowPos] = this._getValue(cols[colPos]);
|
||||
} else {
|
||||
columnData[colPos][rowPos] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(colPos=0; colPos<columnData.length; colPos++) {
|
||||
if(this._summaryDefs[colPos] && this._summaryDefs[colPos].func) {
|
||||
var summaryVal = this._summaryDefs[colPos].func.apply(this,[columnData[colPos]]);
|
||||
this._summaryDefs[colPos].col.innerHTML = summaryVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getSummaryFunction: function(func) {
|
||||
if(this[func] && typeof this[func] == "function") {
|
||||
// local
|
||||
return this[func];
|
||||
} else if(window[func] && typeof window[func] == "function") {
|
||||
// global
|
||||
return window[func];
|
||||
} else {
|
||||
// not existing
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
_getValue: function(col) {
|
||||
var inputNode = $$('input', col);
|
||||
if(inputNode[0]) {
|
||||
return $F(inputNode[0]);
|
||||
}
|
||||
var selectNode = $$('select', col);
|
||||
if(selectNode[0]) {
|
||||
return $F(selectNode[0]);
|
||||
}
|
||||
return col.innerHTML.stripTags();
|
||||
},
|
||||
|
||||
/**
|
||||
* ############# Summary Functions ##############
|
||||
*/
|
||||
|
||||
sum: function(arr) {
|
||||
var sum = 0;
|
||||
arr.each(function(val) {
|
||||
sum += val*1; // convert to float
|
||||
});
|
||||
return sum;
|
||||
},
|
||||
|
||||
sumCurrency: function(arr) {
|
||||
var sum = 0;
|
||||
arr.each(function(val) {
|
||||
if(!val) return;
|
||||
val = val.replace(/\$/,'');
|
||||
val = val.replace(/\,/,'');
|
||||
sum+= val*1; // convert to float
|
||||
});
|
||||
return sum.toCurrency();
|
||||
},
|
||||
|
||||
max: function(arr) {
|
||||
return arr.max();
|
||||
},
|
||||
|
||||
min: function(arr) {
|
||||
return arr.min();
|
||||
}
|
||||
}
|
||||
|
||||
TableListRecord = Class.create();
|
||||
TableListRecord.prototype = {
|
||||
|
||||
onmouseover : function() {
|
||||
Element.addClassName(this,'over');
|
||||
},
|
||||
|
||||
onmouseout : function() {
|
||||
Element.removeClassName(this,'over');
|
||||
},
|
||||
|
||||
ajaxRequest : function(url, subform) {
|
||||
var self = this;
|
||||
// Highlight the new row
|
||||
if(this.parentNode.selectedRow) {
|
||||
Element.removeClassName(this.parentNode.selectedRow,'current');
|
||||
}
|
||||
this.parentNode.selectedRow = this;
|
||||
Element.addClassName(this,'current');
|
||||
|
||||
this.subform = document.getElementById(subform);
|
||||
Element.addClassName(this, 'loading');
|
||||
statusMessage('loading');
|
||||
jQuery.ajax({
|
||||
'url': url + this.id.replace('record-',''),
|
||||
'method' : 'post',
|
||||
'data' : {'ajax': 1},
|
||||
success : function() {
|
||||
self.select_success();
|
||||
},
|
||||
failure : ajaxErrorHandler
|
||||
});
|
||||
},
|
||||
|
||||
getRecordId: function(){
|
||||
parts = this.id.match( /.*[\-]{1}(\d+)$/ );
|
||||
if(parts) return parts[1];
|
||||
else return false;
|
||||
},
|
||||
|
||||
select_success : function(response) {
|
||||
Element.removeClassName(this, 'loading');
|
||||
this.subform.loadNewPage(response.responseText);
|
||||
|
||||
statusMessage('loaded','good');
|
||||
}
|
||||
}
|
||||
|
||||
TableListRecord.applyTo('div.TableListField tr');
|
||||
TableListField.applyTo('div.TableListField');
|
||||
|
||||
Number.prototype.CURRENCIES = {
|
||||
en_GB: '$ ###,###.##'
|
||||
};
|
||||
|
||||
/**
|
||||
* Caution: Not finished!
|
||||
* @param iso string (Not used) Please use in combination with Number.CURRENCIES to achieve i18n
|
||||
* @return string
|
||||
*
|
||||
* @see http://www.jibbering.com/faq/faq_notes/type_convert.html
|
||||
* @see http://www.rgagnon.com/jsdetails/js-0063.html
|
||||
* @see http://www.mredkj.com/javascript/nfdocs.html
|
||||
*/
|
||||
Number.prototype.toCurrency = function(iso) {
|
||||
if(!iso) iso = SS_DEFAULT_ISO;
|
||||
// TODO stub, please implement properly
|
||||
return "$" + this.toFixed(2);
|
||||
}
|
41
lang/en.yml
41
lang/en.yml
@ -81,18 +81,6 @@ en:
|
||||
CheckboxField:
|
||||
NO: No
|
||||
YES: Yes
|
||||
ComplexTableField:
|
||||
CLOSEPOPUP: 'Close Popup'
|
||||
SUCCESSADD2: 'Added {name}'
|
||||
SUCCESSEDIT: 'Saved %s %s %s'
|
||||
ComplexTableField.ss:
|
||||
ADDITEM: 'Add %s'
|
||||
NOITEMSFOUND: 'No items found'
|
||||
SORTASC: 'Sort ascending'
|
||||
SORTDESC: 'Sort descending'
|
||||
ComplexTableField_popup.ss:
|
||||
NEXT: Next
|
||||
PREVIOUS: Previous
|
||||
ConfirmedPasswordField:
|
||||
ATLEAST: 'Passwords must be at least {min} characters long.'
|
||||
BETWEEN: 'Passwords must be {min} to {max} characters long.'
|
||||
@ -175,18 +163,7 @@ en:
|
||||
XlsType: 'Excel spreadsheet'
|
||||
ZipType: 'ZIP compressed file'
|
||||
FileIFrameField:
|
||||
ATTACH: 'Attach {type}'
|
||||
ATTACHONCESAVED: '{type}s can be attached once you have saved the record for the first time.'
|
||||
ATTACHONCESAVED2: 'Files can be attached once you have saved the record for the first time.'
|
||||
DELETE: 'Delete {type}'
|
||||
DISALLOWEDFILETYPE: 'This filetype is not allowed to be uploaded'
|
||||
FILE: File
|
||||
FROMCOMPUTER: 'From your Computer'
|
||||
FROMFILESTORE: 'From the File Store'
|
||||
NOSOURCE: 'Please select a source file to attach'
|
||||
REPLACE: 'Replace {type}'
|
||||
FileIFrameField_iframe.ss:
|
||||
TITLE: 'Image Uploading Iframe'
|
||||
Filesystem:
|
||||
SYNCRESULTS: 'Sync complete: {createdcount} items created, {deletedcount} items deleted'
|
||||
Folder:
|
||||
@ -312,8 +289,6 @@ en:
|
||||
Image:
|
||||
PLURALNAME: Files
|
||||
SINGULARNAME: File
|
||||
ImageField:
|
||||
IMAGE: Image
|
||||
Image_Cached:
|
||||
PLURALNAME: Files
|
||||
SINGULARNAME: File
|
||||
@ -476,10 +451,6 @@ en:
|
||||
UserPermissionsIntro: 'Assigning groups to this user will adjust the permissions they have. See the groups section for details of permissions on individual groups.'
|
||||
PhoneNumberField:
|
||||
VALIDATION: 'Please enter a valid phone number'
|
||||
RelationComplexTableField.ss:
|
||||
ADD: Add
|
||||
CSVEXPORT: 'Export to CSV'
|
||||
NOTFOUND: 'No items found'
|
||||
Security:
|
||||
ALREADYLOGGEDIN: 'You don''t have access to this page. If you have another account that can access that page, you can log in again below.'
|
||||
BUTTONSEND: 'Send me the password reset link'
|
||||
@ -517,24 +488,12 @@ en:
|
||||
FileFieldLabel: 'CSV File <small>(Allowed extensions: *.csv)</small>'
|
||||
SilverStripeNavigator:
|
||||
Edit: Edit
|
||||
SimpleImageField:
|
||||
NOUPLOAD: 'No Image Uploaded'
|
||||
SiteTree:
|
||||
TABMAIN: Main
|
||||
TableField:
|
||||
ISREQUIRED: 'In %s ''%s'' is required'
|
||||
TableField.ss:
|
||||
ADD: 'Add a new row'
|
||||
ADDITEM: 'Add %s'
|
||||
TableListField:
|
||||
CSVEXPORT: 'Export to CSV'
|
||||
PRINT: Print
|
||||
Print: Print
|
||||
SELECT: 'Select:'
|
||||
TableListField.ss:
|
||||
NOITEMSFOUND: 'No items found'
|
||||
SORTASC: 'Sort in ascending order'
|
||||
SORTDESC: 'Sort in descending order'
|
||||
TableListField_PageControls.ss:
|
||||
DISPLAYING: Displaying
|
||||
OF: of
|
||||
|
@ -1,16 +0,0 @@
|
||||
/* table */
|
||||
.ComplexTableField {
|
||||
margin-bottom: 10px;
|
||||
|
||||
tbody td {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tbody td.markingcheckbox {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-dialog .ctf-dialog.ui-dialog-content {
|
||||
padding-right: 0; /* scrollbars */
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
#right form .hasmanyfile a.addFile,
|
||||
#right form .hasmanyfile a.uploadFile,
|
||||
#right form .hasmanyfile .currentFiles li {
|
||||
font-size: 1.2em;
|
||||
padding-left: 3px;
|
||||
}
|
||||
#right form .hasmanyfile a.uploadFile {
|
||||
border-color:#CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
|
||||
border-style:solid;
|
||||
border-width: 2px;
|
||||
color:#333333;
|
||||
cursor:pointer;
|
||||
font-size:11px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -29px;
|
||||
left: 344px;
|
||||
text-decoration: none;
|
||||
overflow:visible;
|
||||
padding:3px 5px;
|
||||
float:left;
|
||||
width:auto;
|
||||
}
|
||||
|
||||
#right form .hasmanyfile a.removeFile {
|
||||
border-color:#CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
|
||||
border-style:solid;
|
||||
border-width: 2px;
|
||||
color:#333333;
|
||||
cursor:pointer;
|
||||
font-size:10px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
overflow:visible;
|
||||
padding:3px 5px;
|
||||
width:auto;
|
||||
margin: -2px 0 0 10px;
|
||||
}
|
||||
#right form .hasmanyfile a.removeFile:hover {
|
||||
background: #CE0000;
|
||||
color: #fff;
|
||||
}
|
||||
#right form .hasmanyfile a.addFile {
|
||||
border-color:#CCCCCC rgb(153, 153, 153) rgb(153, 153, 153) rgb(204, 204, 204);
|
||||
border-style:solid;
|
||||
border-width: 2px;
|
||||
color:#333333;
|
||||
cursor:pointer;
|
||||
font-size:11px;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: -29px;
|
||||
left: 340px;
|
||||
text-decoration: none;
|
||||
overflow:visible;
|
||||
padding:3px 5px;
|
||||
float:left;
|
||||
width:auto;
|
||||
}
|
||||
#right form .hasmanyfile a.addFile:hover,
|
||||
#right form .hasmanyfile a.uploadFile:hover {
|
||||
background: #fff;
|
||||
}
|
||||
#right form .hasmanyfile ul.currentFiles {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles li {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
#right form .hasmanyfile .clear {
|
||||
clear: both;
|
||||
}
|
||||
/* ICONS */
|
||||
#right form .hasmanyfile .currentFiles a[href$=".pdf"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".PDF"],
|
||||
#right form .hasmanyfile .currentFiles a.pdf {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/page_white_acrobat.png) no-repeat left center;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles a[href$=".doc"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".DOC"],
|
||||
#right form .hasmanyfile .currentFiles a.doc {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/page_word.png) no-repeat left center;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles a[href$=".xls"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".XLS"],
|
||||
#right form .hasmanyfile .currentFiles a.xls {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/page_excel.png) no-repeat left center;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles a[href$=".gz"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".GZ"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".gzip"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".GZIP"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".zip"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".ZIP"],
|
||||
#right form .hasmanyfile .currentFiles a.archive {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/page_white_zip.png) no-repeat left center;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles a[href$=".jpg"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".JPG"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".gif"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".GIF"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".png"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".PNG"],
|
||||
#right form .hasmanyfile .currentFiles a.image {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/icon-jpg.gif) no-repeat left center;
|
||||
}
|
||||
#right form .hasmanyfile .currentFiles a[href$=".exe"],
|
||||
#right form .hasmanyfile .currentFiles a[href$=".EXE"],
|
||||
#right form .hasmanyfile .currentFiles a.application {
|
||||
padding: 2px;
|
||||
padding-left: 20px;
|
||||
background: url(../images/icons/application.png) no-repeat left center;
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
table.TableField,
|
||||
table.TableListField,
|
||||
.TableListField table.data,
|
||||
table.CMSList {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width : 100%;
|
||||
}
|
||||
|
||||
/* Preventing IE6 from showing double borders */
|
||||
body>div table.TableField,
|
||||
body>div table.TableListField,
|
||||
body>div .TableListField table.data,
|
||||
body>div table.CMSList {
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
table.TableField td,
|
||||
table.TableListField td,
|
||||
.TableListField table.data td,
|
||||
table.CMSList td {
|
||||
border-style:none;
|
||||
}
|
||||
|
||||
table.TableField th,
|
||||
table.TableListField th,
|
||||
.TableListField table.data th,
|
||||
table.CMSList th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.TableField thead th,
|
||||
.TableListField table.data thead th,
|
||||
table.CMSList thead th {
|
||||
white-space: nowrap;
|
||||
padding: 3px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.TableField thead th span,
|
||||
.TableListField table.data thead th span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
table.TableField thead th span.sortLink,
|
||||
.TableListField table.data thead th span.sortLink,
|
||||
table.CMSList thead th span.sortLink {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
table.TableField tbody td,
|
||||
table.TableField tfoot td,
|
||||
.TableListField table.data tbody td,
|
||||
.TableListField table.data tfoot td,
|
||||
table.CMSList tbody td,
|
||||
table.CMSList tfoot td {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
|
||||
.TableListField table.data tfoot tr.addtogrouprow td {
|
||||
padding:3px;
|
||||
}
|
||||
|
||||
.TableListField table.data tfoot .actions {
|
||||
float: none;
|
||||
}
|
||||
|
||||
.TableListField table.data tfoot tr.addtogrouprow input {
|
||||
width: 94%;
|
||||
}
|
||||
|
||||
.TableField td input,
|
||||
.TableListField td input {
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
table.data tbody td input,
|
||||
table.data tbody td textarea {
|
||||
border:0 !important;
|
||||
}
|
||||
|
||||
table.TableField tbody td.checkbox,
|
||||
.TableListField table.data tbody td.checkbox,
|
||||
table.CMSList tbody td.checkbox {
|
||||
padding-left : 5px;
|
||||
background-image : url(../images/checkbox.png);
|
||||
background-repeat : repeat-x;
|
||||
background-position : left bottom;
|
||||
}
|
||||
|
||||
.TableListField table.data tfoot .addlink img {
|
||||
vertical-align: middle;
|
||||
margin: 3px 6px 3px 3px;
|
||||
}
|
||||
|
||||
.TableListField table.data tfoot tr td a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.TableListField table.data tbody tr td a:hover,
|
||||
.TableListField table.data tfoot tr td a:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a loading indication on a TableListField row
|
||||
*/
|
||||
.TableListField tr.loading td.first {
|
||||
padding-left: 22px;
|
||||
background: url(../images/network-save.gif) 3px 2px no-repeat;
|
||||
}
|
||||
|
||||
.right form .TableField span.readonly {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.right form .TableListField td {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.TableListField div.utility {
|
||||
overflow: auto;
|
||||
}
|
||||
.TableListField div.utility .item {
|
||||
margin-top: 1em;
|
||||
padding: 3px 0 6px 0;
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
.TableListField div.utility a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
margin-right: 2px;
|
||||
overflow: visible;
|
||||
padding: 3px 2px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
form .TableField .message {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.TableListField .selectOptions {
|
||||
overflow: auto;
|
||||
font: 1.3em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.TableListField .selectOptions li {
|
||||
float: left;
|
||||
margin: 0px 5px;
|
||||
}
|
||||
|
||||
.TableListField .PageControls {
|
||||
margin: 5px 0;
|
||||
text-align:center;
|
||||
display:block;
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
.TableListField .PageControls * {
|
||||
display:inline;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
.TableListField .PageControls .Last{
|
||||
display: block;
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
.TableListField .PageControls .First{
|
||||
float:left; display:block;
|
||||
width:40px; text-align:left;
|
||||
}
|
||||
|
||||
|
||||
#Pagination {
|
||||
margin-top: 10px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
span {
|
||||
|
||||
display: inline;
|
||||
font-size: 14px;
|
||||
}
|
||||
div {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
#Pagination_Next a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#Pagination_Next a div {
|
||||
position: relative;
|
||||
left: -20px;
|
||||
}
|
||||
#Pagination_Next a img {
|
||||
position: relative;
|
||||
top: -15px;
|
||||
left: 5px;
|
||||
}
|
||||
#Pagination_Previous a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#Pagination_Previous a img {
|
||||
position: relative;
|
||||
top: -15px;
|
||||
left: 35px;
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
<div id="$id" class="$CSSClasses $extraClass field nolabel" href="$CurrentLink">
|
||||
<div class="middleColumn">
|
||||
<% if Markable %>
|
||||
<% include TableListField_SelectOptions %>
|
||||
<% end_if %>
|
||||
<% include TableListField_PageControls %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<% loop Headings %>
|
||||
<th class="$Name">
|
||||
<% if IsSortable %>
|
||||
<span class="sortTitle">
|
||||
<a href="$SortLink">$Title</a>
|
||||
</span>
|
||||
<span class="sortLink <% if SortBy %><% else %>sortLinkHidden<% end_if %>">
|
||||
<a href="$SortLink">
|
||||
<% if SortDirection = desc %>
|
||||
<img src="$ModulePath(framework)/images/bullet_arrow_up.png" alt="<% _t('ComplexTableField.ss.SORTASC', 'Sort ascending') %>" />
|
||||
<% else %>
|
||||
<img src="$ModulePath(framework)/images/bullet_arrow_down.png" alt="<% _t('ComplexTableField.ss.SORTDESC', 'Sort descending') %>" />
|
||||
<% end_if %>
|
||||
</a>
|
||||
|
||||
</span>
|
||||
<% else %>
|
||||
$Title
|
||||
<% end_if %>
|
||||
</th>
|
||||
<% end_loop %>
|
||||
<% loop Actions %><th width="18"> </th><% end_loop %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<% if HasSummary %>
|
||||
<tr class="summary">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td><i>$SummaryTitle</i></td>
|
||||
<% loop SummaryFields %>
|
||||
<td<% if Function %> class="$Function"<% end_if %>>$SummaryValue</td>
|
||||
<% end_loop %>
|
||||
<% loop Actions %><td width="18"> </td><% end_loop %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
<% if Can(add) %>
|
||||
<tr>
|
||||
<% if Markable %><td width="18"> </td><% end_if %>
|
||||
<td colspan="$ItemCount">
|
||||
<input type="hidden" id="{$id}_PopupHeight" value="$PopupHeight" disabled="disabled">
|
||||
<input type="hidden" id="{$id}_PopupWidth" value="$PopupWidth" disabled="disabled">
|
||||
<a class="popuplink addlink" href="$AddLink" alt="add"><img src="$ModulePath(framework)/images/add.gif" alt="<% _t('ComplexTableField.ss.ADDITEM', 'add') %>" />
|
||||
<% sprintf(_t('ADDITEM', 'Add %s', 'Add [name]'),$Title) %>
|
||||
</a>
|
||||
</td>
|
||||
<% loop Actions %><td width="18"> </td><% end_loop %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<% if Items %>
|
||||
<% loop Items %>
|
||||
<% include TableListField_Item %>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<tr class="notfound">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td colspan="$Headings.Count"><i><% _t('ComplexTableField.ss.NOITEMSFOUND', 'No items found') %></i></td>
|
||||
<% loop Actions %><td width="18"> </td><% end_loop %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% if Utility %>
|
||||
<div class="utility">
|
||||
<% loop Utility %>
|
||||
<span class="item"><a href="$Link" target="_blank">$Title</a></span>
|
||||
<% end_loop %>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</div>
|
||||
</div>
|
@ -1,42 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
|
||||
<% base_tag %>
|
||||
</head>
|
||||
<body class="cms" style="overflow: auto;">
|
||||
<div class="right $PopupClasses">
|
||||
$DetailForm
|
||||
</div>
|
||||
|
||||
<% if ShowPagination %>
|
||||
<table id="ComplexTableField_Pagination">
|
||||
<tr>
|
||||
<% if Paginator.PrevLink %>
|
||||
<td id="ComplexTableField_Pagination_Previous">
|
||||
<a href="$Paginator.PrevLink"><img src="$ModulePath(framework)/images/pagination/record-prev.png" /><% _t('ComplexTableField_popup.ss.PREVIOUS', 'Previous') %></a>
|
||||
</td>
|
||||
<% end_if %>
|
||||
<% if xdsfdsf %>
|
||||
<% else %>
|
||||
<td>
|
||||
<% loop Paginator.Pages %>
|
||||
<% if active %>
|
||||
<a href="$link">$number</a>
|
||||
<% else %>
|
||||
<span>$number</span>
|
||||
<% end_if %>
|
||||
<% end_loop %>
|
||||
</td>
|
||||
<% end_if %>
|
||||
<% if Paginator.NextLink %>
|
||||
<td id="ComplexTableField_Pagination_Next">
|
||||
<a href="$Paginator.NextLink"><% _t('ComplexTableField_popup.ss.NEXT', 'Next') %><img src="$ModulePath(framework)/images/pagination/record-next.png" /></a>
|
||||
</td>
|
||||
<% end_if %>
|
||||
</tr>
|
||||
</table>
|
||||
<% end_if %>
|
||||
</body>
|
||||
</html>
|
@ -1,67 +0,0 @@
|
||||
<div id="$id" class="$CSSClasses $extraClass field" href="$CurrentLink">
|
||||
<% if Markable %>
|
||||
<% include TableListField_SelectOptions %>
|
||||
<% end_if %>
|
||||
<% include TableListField_PageControls %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<% loop Headings %>
|
||||
<th class="$Name">$Title</th>
|
||||
<% end_loop %>
|
||||
<% if Can(show) %><th width="18"> </th><% end_if %>
|
||||
<% if Can(edit) %><th width="18"> </th><% end_if %>
|
||||
<% if Can(delete) %><th width="18"> </th><% end_if %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<% if HasSummary %>
|
||||
<tr class="summary">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td><i>$SummaryTitle</i></td>
|
||||
<% loop SummaryFields %>
|
||||
<td<% if Function %> class="$Function"<% end_if %>> </td>
|
||||
<% end_loop %>
|
||||
<% if Can(show) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(edit) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
<% if Can(add) %>
|
||||
<tr>
|
||||
<% if Markable %><td width="18"> </td><% end_if %>
|
||||
<td colspan="$ItemCount">
|
||||
<input type="hidden" id="{$id}_PopupHeight" value="$PopupHeight" disabled="disabled">
|
||||
<input type="hidden" id="{$id}_PopupWidth" value="$PopupWidth" disabled="disabled">
|
||||
<a class="popuplink addlink" href="$AddLink" alt="<% _t('RelationComplexTableField.ss.ADD', 'Add') %>"><img src="$ModulePath(framework)/images/add.gif" alt="<% _t('ADD', 'Add') %>" /><% _t('RelationComplexTableField.ss.ADD', 'Add') %> $Title</a>
|
||||
</td>
|
||||
<% if Can(show) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(edit) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<% if Items %>
|
||||
<% loop Items %>
|
||||
<% include TableListField_Item %>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<tr class="notfound">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td colspan="$Headings.Count"><i><% _t('RelationComplexTableField.ss.NOTFOUND', 'No items found') %></i></td>
|
||||
<% if Can(show) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(edit) %><td width="18"> </td><% end_if %>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tbody>
|
||||
</table>
|
||||
$ExtraData
|
||||
<div class="utility">
|
||||
<% if Can(export) %>
|
||||
<a href="$ExportLink" target="_blank"><% _t('RelationComplexTableField.ss.CSVEXPORT', 'Export to CSV' ) %></a>
|
||||
<% end_if %>
|
||||
</div>
|
||||
</div>
|
@ -1,71 +0,0 @@
|
||||
<div id="$id" class="$CSSClasses field">
|
||||
<div class="middleColumn">
|
||||
<% include TableListField_PageControls %>
|
||||
<% if Message %>
|
||||
<p id="{$id}_error" class="message $MessageType">$Message</p>
|
||||
<% else %>
|
||||
<p id="{$id}_error" class="message $MessageType" style="display: none"></p>
|
||||
<% end_if %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<% loop Headings %>
|
||||
<th class="$Name $Class" scope="col">$Title</th>
|
||||
<% end_loop %>
|
||||
<th style="display: none"></th>
|
||||
<% if Can(delete) %><th width="18"> </th><% end_if %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot>
|
||||
<% if HasSummary %>
|
||||
<tr class="summary">
|
||||
<td><i>$SummaryTitle</i></td>
|
||||
<% loop SummaryFields %>
|
||||
<td<% if Function %> class="$Function"<% end_if %>>$SummaryValue</td>
|
||||
<% end_loop %>
|
||||
<th style="display: none"></th>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
<% if Can(add) %>
|
||||
<tr>
|
||||
<td colspan="$ItemCount">
|
||||
<a href="#" class="addrow" title="<% _t('TableField.ss.ADD', 'Add a new row') %>"><img src="$ModulePath(framework)/images/add.gif" alt="<% _t('TableField.ss.ADD','Add a new row') %>" />
|
||||
<% sprintf(_t('TableField.ss.ADDITEM','Add %s'),$Title) %>
|
||||
</a>
|
||||
</td>
|
||||
<td style="display: none"></td>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<% if Items %>
|
||||
<% loop Items %>
|
||||
<tr id="record-$Parent.id-$ID" class="row<% if HighlightClasses %> $HighlightClasses<% end_if %>">
|
||||
<% loop Fields %>
|
||||
<td class="$FieldClass $extraClass $ClassName $Title tablecolumn">$Field</td>
|
||||
<% end_loop %>
|
||||
<td style="display: none">$ExtraData</td>
|
||||
<% if Can(delete) %><td width="18"><a class="deletelink" href="$DeleteLink" title="<% _t('TableField.ss.DELETEROW') %>"><img src="$ModulePath(framework)/images/delete.gif" alt="<% _t('TableField.ss.DELETE') %>" /></a></td><% end_if %>
|
||||
</tr>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<tr class="notfound">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td colspan="$Headings.Count"><i><% _t('TableField.ss.NOITEMSFOUND') %></i></td>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% if Print %><% else %><div class="utility">
|
||||
<% loop Utility %>
|
||||
<span class="item"><a href="$Link">$Title</a></span>
|
||||
<% end_loop %>
|
||||
</div><% end_if %>
|
||||
<% if Message %>
|
||||
<span class="message $MessageType">$Message</span>
|
||||
<% end_if %>
|
||||
</div>
|
||||
</div>
|
@ -1,85 +0,0 @@
|
||||
<div id="$id" class="$CSSClasses $extraClass field nolabel">
|
||||
<% if Print %><% else %>
|
||||
<% if Markable %>
|
||||
<% include TableListField_SelectOptions %>
|
||||
<% end_if %>
|
||||
<% include TableListField_PageControls %>
|
||||
<% end_if %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<% if Markable %><th width="16"><% if MarkableTitle %>$MarkableTitle<% else %> <% end_if %></th><% end_if %>
|
||||
<% if Print %>
|
||||
<% loop Headings %>
|
||||
<th class="$Name">
|
||||
$Title
|
||||
</th>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<% loop Headings %>
|
||||
<th class="$Name">
|
||||
<% if IsSortable %>
|
||||
<span class="sortTitle">
|
||||
<a href="$SortLink">$Title</a>
|
||||
</span>
|
||||
<span class="sortLink <% if SortBy %><% else %>sortLinkHidden<% end_if %>">
|
||||
<% if SortDirection = desc %>
|
||||
<a href="$SortLink"><img src="$ModulePath(framework)/images/bullet_arrow_up.png" alt="<% _t('TableListField.ss.SORTDESC', 'Sort in descending order') %>" /></a>
|
||||
<% else %>
|
||||
<a href="$SortLink"><img src="$ModulePath(framework)/images/bullet_arrow_down.png" alt="<% _t('TableListField.ss.SORTASC', 'Sort in ascending order') %>" /></a>
|
||||
<% end_if %>
|
||||
</a>
|
||||
|
||||
</span>
|
||||
<% else %>
|
||||
<span>$Title</span>
|
||||
<% end_if %>
|
||||
</th>
|
||||
<% end_loop %>
|
||||
<% end_if %>
|
||||
<% if Can(delete) %><th width="18"> </th><% end_if %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<% if HasSummary %>
|
||||
<tfoot>
|
||||
<tr class="summary">
|
||||
<% include TableListField_Summary %>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<% end_if %>
|
||||
|
||||
<tbody>
|
||||
<% if HasGroupedItems %>
|
||||
<% loop GroupedItems %>
|
||||
<% loop Items %>
|
||||
<% include TableListField_Item %>
|
||||
<% end_loop %>
|
||||
<tr class="summary partialSummary">
|
||||
<% include TableListField_Summary %>
|
||||
</tr>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<% if Items %>
|
||||
<% loop Items %>
|
||||
<% include TableListField_Item %>
|
||||
<% end_loop %>
|
||||
<% else %>
|
||||
<tr class="notfound">
|
||||
<% if Markable %><th width="18"> </th><% end_if %>
|
||||
<td colspan="$Headings.Count"><i><% _t('TableListField.ss.NOITEMSFOUND','No items found') %></i></td>
|
||||
<% if Can(delete) %><td width="18"> </td><% end_if %>
|
||||
</tr>
|
||||
<% end_if %>
|
||||
<% if Can(add) %>
|
||||
$AddRecordAsTableRow
|
||||
<% end_if %>
|
||||
<% end_if %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% if Print %><% else %><div class="utility">
|
||||
<% loop Utility %>
|
||||
<span class="item"><a href="$Link">$Title</a></span>
|
||||
<% end_loop %>
|
||||
</div><% end_if %>
|
||||
</div>
|
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<% base_tag %>
|
||||
<title>Print</title>
|
||||
</head>
|
||||
|
||||
<body onload="window.print();">
|
||||
<% include TableListField %>
|
||||
</body>
|
||||
</html>
|
@ -1,202 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package framework
|
||||
* @subpackage tests
|
||||
*/
|
||||
class ComplexTableFieldTest extends FunctionalTest {
|
||||
|
||||
static $fixture_file = 'ComplexTableFieldTest.yml';
|
||||
static $use_draft_site = true;
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'ComplexTableFieldTest_Player',
|
||||
'ComplexTableFieldTest_Team',
|
||||
'ComplexTableFieldTest_Sponsor',
|
||||
);
|
||||
|
||||
/**
|
||||
* An instance of {@link Controller} used for
|
||||
* running tests against.
|
||||
*
|
||||
* @var Controller object
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
protected $autoFollowRedirection = false;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->controller = new ComplexTableFieldTest_Controller();
|
||||
$this->manyManyForm = $this->controller->ManyManyForm();
|
||||
}
|
||||
|
||||
public function testCorrectNumberOfRowsInTable() {
|
||||
$field = $this->manyManyForm->Fields()->dataFieldByName('Players');
|
||||
$parser = new CSSContentParser($field->FieldHolder());
|
||||
|
||||
$this->assertEquals(count($parser->getBySelector('tbody tr')), 2, 'There are 2 players (rows) in the table');
|
||||
$this->assertEquals($field->Items()->Count(), 2, 'There are 2 CTF items in the SS_List');
|
||||
}
|
||||
|
||||
public function testAddingManyManyNewPlayer() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
$team = DataObject::get_one('ComplexTableFieldTest_Team', "\"Name\" = 'The Awesome People'");
|
||||
|
||||
$this->post('ComplexTableFieldTest_Controller/ManyManyForm/field/Players/AddForm', array(
|
||||
'Name' => 'Bobby Joe',
|
||||
'ctf' => array(
|
||||
'ClassName' => 'ComplexTableFieldTest_Player',
|
||||
'manyManyRelation' => 'Players',
|
||||
'parentClass' => 'ComplexTableFieldTest_Team',
|
||||
'sourceID' => $team->ID
|
||||
)
|
||||
));
|
||||
|
||||
$newPlayer = DataObject::get_one('ComplexTableFieldTest_Player', "\"Name\" = 'Bobby Joe'");
|
||||
$this->assertNotNull($newPlayer, 'A new ComplexTableFieldTest_Player record was created, Name = "Bobby Joe"');
|
||||
$teams = $newPlayer->getManyManyComponents('Teams');
|
||||
$this->assertEquals($teams->Count(), 1, 'Automatic many-many relation was set correctly on the new player');
|
||||
}
|
||||
|
||||
public function testAddingHasManyData() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
|
||||
$team = DataObject::get_one('ComplexTableFieldTest_Team', "\"Name\" = 'The Awesome People'");
|
||||
|
||||
$this->post('ComplexTableFieldTest_Controller/HasManyForm/field/Sponsors/AddForm', array(
|
||||
'Name' => 'Jim Beam',
|
||||
'ctf' => array(
|
||||
'ClassName' => 'ComplexTableFieldTest_Sponsor',
|
||||
'hasManyRelation' => 'Sponsors',
|
||||
'parentClass' => 'ComplexTableFieldTest_Team',
|
||||
'sourceID' => $team->ID
|
||||
)
|
||||
));
|
||||
|
||||
$newSponsor = DataObject::get_one('ComplexTableFieldTest_Sponsor', "\"Name\" = 'Jim Beam'");
|
||||
$this->assertNotNull($newSponsor, 'A new ComplexTableFieldTest_Sponsor record was created, Name = "Jim Beam"');
|
||||
$this->assertEquals($newSponsor->TeamID, $team->ID,
|
||||
'Automatic has-many/has-one relation was set correctly on the sponsor');
|
||||
$this->assertEquals($newSponsor->getComponent('Team')->ID, $team->ID,
|
||||
'Automatic has-many/has-one relation was set correctly on the sponsor');
|
||||
|
||||
$team = DataObject::get_by_id('ComplexTableFieldTest_Team', $team->ID);
|
||||
$sponsor = DataObject::get_by_id('ComplexTableFieldTest_Sponsor', $newSponsor->ID);
|
||||
$this->assertEquals($newSponsor->ID, $sponsor->ID, 'The sponsor is the same as the one we added');
|
||||
$foundTeam = $sponsor->getComponent('Team');
|
||||
$this->assertEquals($team->ID, $foundTeam->ID, 'The team ID matches on the other side of the relation');
|
||||
}
|
||||
|
||||
}
|
||||
class ComplexTableFieldTest_Controller extends Controller {
|
||||
|
||||
public function Link($action = null) {
|
||||
return "ComplexTableFieldTest_Controller/$action";
|
||||
}
|
||||
|
||||
public function ManyManyForm() {
|
||||
$team = DataObject::get_one('ComplexTableFieldTest_Team', "\"Name\" = 'The Awesome People'");
|
||||
|
||||
$playersField = new ComplexTableField(
|
||||
$this,
|
||||
'Players',
|
||||
$team->Players(),
|
||||
ComplexTableFieldTest_Player::$summary_fields,
|
||||
'getCMSFields'
|
||||
);
|
||||
|
||||
$form = new Form(
|
||||
$this,
|
||||
'ManyManyForm',
|
||||
new FieldList(
|
||||
new HiddenField('ID', '', $team->ID),
|
||||
$playersField
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('doSubmit', 'Submit')
|
||||
)
|
||||
);
|
||||
$form->loadDataFrom($team);
|
||||
|
||||
$form->disableSecurityToken();
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function HasManyForm() {
|
||||
$team = DataObject::get_one('ComplexTableFieldTest_Team', "\"Name\" = 'The Awesome People'");
|
||||
|
||||
$sponsorsField = new ComplexTableField(
|
||||
$this,
|
||||
'Sponsors',
|
||||
$team->Sponsors(),
|
||||
ComplexTableFieldTest_Sponsor::$summary_fields,
|
||||
'getCMSFields'
|
||||
);
|
||||
|
||||
$form = new Form(
|
||||
$this,
|
||||
'HasManyForm',
|
||||
new FieldList(
|
||||
new HiddenField('ID', '', $team->ID),
|
||||
$sponsorsField
|
||||
),
|
||||
new FieldList(
|
||||
new FormAction('doSubmit', 'Submit')
|
||||
)
|
||||
);
|
||||
$form->loadDataFrom($team);
|
||||
|
||||
$form->disableSecurityToken();
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
}
|
||||
class ComplexTableFieldTest_Player extends DataObject implements TestOnly {
|
||||
|
||||
public static $db = array(
|
||||
'Name' => 'Varchar(100)'
|
||||
);
|
||||
|
||||
public static $many_many = array(
|
||||
'Teams' => 'ComplexTableFieldTest_Team'
|
||||
);
|
||||
|
||||
public static $many_many_extraFields = array(
|
||||
'Teams' => array(
|
||||
'Role' => 'Varchar(100)',
|
||||
'Position' => "Enum('Admin,Player,Coach','Admin')",
|
||||
'DateJoined' => 'Date'
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
class ComplexTableFieldTest_Team extends DataObject implements TestOnly {
|
||||
|
||||
public static $db = array(
|
||||
'Name' => 'Varchar(100)'
|
||||
);
|
||||
|
||||
public static $belongs_many_many = array(
|
||||
'Players' => 'ComplexTableFieldTest_Player'
|
||||
);
|
||||
|
||||
public static $has_many = array(
|
||||
'Sponsors' => 'ComplexTableFieldTest_Sponsor'
|
||||
);
|
||||
|
||||
}
|
||||
class ComplexTableFieldTest_Sponsor extends DataObject implements TestOnly {
|
||||
|
||||
public static $db = array(
|
||||
'Name' => 'Varchar(100)'
|
||||
);
|
||||
|
||||
public static $has_one = array(
|
||||
'Team' => 'ComplexTableFieldTest_Team'
|
||||
);
|
||||
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
ComplexTableFieldTest_Player:
|
||||
p1:
|
||||
Name: Joe Bloggs
|
||||
p2:
|
||||
Name: Some Guy
|
||||
ComplexTableFieldTest_Team:
|
||||
t1:
|
||||
Name: The Awesome People
|
||||
Players: =>ComplexTableFieldTest_Player.p1,=>ComplexTableFieldTest_Player.p2
|
||||
t2:
|
||||
Name: Incredible Four
|
||||
ComplexTableFieldTest_Sponsor:
|
||||
s1:
|
||||
Name: Coca Cola
|
||||
s2:
|
||||
Name: Pepsi
|
@ -1,263 +0,0 @@
|
||||
<?php
|
||||
|
||||
class TableFieldTest extends SapphireTest {
|
||||
static $fixture_file = 'TableFieldTest.yml';
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'TableFieldTest_Object',
|
||||
'TableFieldTest_HasManyRelation',
|
||||
);
|
||||
|
||||
public function testAdd() {
|
||||
$group = $this->objFromFixture('Group','group1_no_perms');
|
||||
|
||||
$tableField = new TableField(
|
||||
"Permissions",
|
||||
"Permission",
|
||||
array(
|
||||
"Code" => 'Code',
|
||||
"Arg" => 'Arg',
|
||||
),
|
||||
array(
|
||||
"Code" => "TextField",
|
||||
"Arg" => "TextField",
|
||||
),
|
||||
"GroupID",
|
||||
$group->ID
|
||||
);
|
||||
$form = new Form(
|
||||
new TableFieldTest_Controller(),
|
||||
"Form",
|
||||
new FieldList($tableField),
|
||||
new FieldList()
|
||||
);
|
||||
|
||||
// Test Insert
|
||||
|
||||
// The field starts emppty. Save some new data.
|
||||
// We have replicated the array structure that the specific layout of the form generates.
|
||||
$tableField->setValue(array(
|
||||
'new' => array(
|
||||
'Code' => array(
|
||||
'CustomPerm1',
|
||||
'CustomPerm2',
|
||||
),
|
||||
'Arg' => array(
|
||||
'1',
|
||||
'2'
|
||||
),
|
||||
),
|
||||
));
|
||||
$tableField->saveInto($group);
|
||||
|
||||
// Let's check that the 2 permissions entries have been saved
|
||||
$permissions = $group->Permissions()->map('Arg', 'Code');
|
||||
$this->assertEquals(array(
|
||||
1 => 'CustomPerm1',
|
||||
2 => 'CustomPerm2',
|
||||
), $permissions->toArray());
|
||||
|
||||
|
||||
// Test repeated insert
|
||||
$value = array();
|
||||
foreach($group->Permissions() as $permission) {
|
||||
$value[$permission->ID] = array("Code" => $permission->Code, "Arg" => $permission->Arg);
|
||||
}
|
||||
$value['new'] = array(
|
||||
'Code' => array(
|
||||
'CustomPerm3',
|
||||
),
|
||||
'Arg' => array(
|
||||
'3',
|
||||
),
|
||||
);
|
||||
$tableField->setValue($value);
|
||||
$tableField->saveInto($group);
|
||||
|
||||
// Let's check that the 2 existing permissions entries, and the 1 new one, have been saved
|
||||
$permissions = $group->Permissions()->map('Arg', 'Code');
|
||||
$this->assertEquals(array(
|
||||
1 => 'CustomPerm1',
|
||||
2 => 'CustomPerm2',
|
||||
3 => 'CustomPerm3',
|
||||
), $permissions->toArray());
|
||||
|
||||
}
|
||||
|
||||
public function testEdit() {
|
||||
$group = $this->objFromFixture('Group','group2_existing_perms');
|
||||
$perm1 = $this->objFromFixture('Permission', 'perm1');
|
||||
$perm2 = $this->objFromFixture('Permission', 'perm2');
|
||||
|
||||
$tableField = new TableField(
|
||||
"Permissions",
|
||||
"Permission",
|
||||
array(
|
||||
"Code" => 'Code',
|
||||
"Arg" => 'Arg',
|
||||
),
|
||||
array(
|
||||
"Code" => "TextField",
|
||||
"Arg" => "TextField",
|
||||
),
|
||||
"GroupID",
|
||||
$group->ID
|
||||
);
|
||||
$form = new Form(
|
||||
new TableFieldTest_Controller(),
|
||||
"Form",
|
||||
new FieldList($tableField),
|
||||
new FieldList()
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $tableField->sourceItems()->Count());
|
||||
|
||||
// We have replicated the array structure that the specific layout of the form generates.
|
||||
$tableField->setValue(array(
|
||||
$perm1->ID => array(
|
||||
'Code' => 'Perm1 Modified',
|
||||
'Arg' => '101'
|
||||
),
|
||||
$perm2->ID => array(
|
||||
'Code' => 'Perm2 Modified',
|
||||
'Arg' => '102'
|
||||
)
|
||||
));
|
||||
$tableField->saveInto($group);
|
||||
|
||||
// Let's check that the 2 permissions entries have been saved
|
||||
$permissions = $group->Permissions()->map('Arg', 'Code');
|
||||
$this->assertEquals(array(
|
||||
101 => 'Perm1 Modified',
|
||||
102 => 'Perm2 Modified',
|
||||
), $permissions->toArray());
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
$group = $this->objFromFixture('Group','group2_existing_perms');
|
||||
$perm1 = $this->objFromFixture('Permission', 'perm1');
|
||||
$perm2 = $this->objFromFixture('Permission', 'perm2');
|
||||
|
||||
$tableField = new TableField(
|
||||
"Permissions",
|
||||
"Permission",
|
||||
array(
|
||||
"Code" => 'Code',
|
||||
"Arg" => 'Arg',
|
||||
),
|
||||
array(
|
||||
"Code" => "TextField",
|
||||
"Arg" => "TextField",
|
||||
),
|
||||
"GroupID",
|
||||
$group->ID
|
||||
);
|
||||
$form = new Form(
|
||||
new TableFieldTest_Controller(),
|
||||
"Form",
|
||||
new FieldList($tableField),
|
||||
new FieldList()
|
||||
);
|
||||
|
||||
$this->assertContains($perm1->ID, $tableField->sourceItems()->column('ID'));
|
||||
|
||||
$response = $tableField->Items()->find('ID', $perm1->ID)->delete();
|
||||
|
||||
$this->assertNotContains($perm1->ID, $tableField->sourceItems()->column('ID'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Relation auto-setting is now the only option
|
||||
*/
|
||||
public function testAutoRelationSettingOn() {
|
||||
$o = new TableFieldTest_Object();
|
||||
$o->write();
|
||||
|
||||
$tf = new TableField(
|
||||
'HasManyRelations',
|
||||
'TableFieldTest_HasManyRelation',
|
||||
array(
|
||||
'Value' => 'Value'
|
||||
),
|
||||
array(
|
||||
'Value' => 'TextField'
|
||||
)
|
||||
);
|
||||
|
||||
// Test with auto relation setting
|
||||
$form = new Form(new TableFieldTest_Controller(), "Form", new FieldList($tf), new FieldList());
|
||||
$form->loadDataFrom($o);
|
||||
|
||||
$tf->setValue(array(
|
||||
'new' => array(
|
||||
'Value' => array('one','two',)
|
||||
)
|
||||
));
|
||||
|
||||
$form->saveInto($o);
|
||||
$this->assertEquals(2, $o->HasManyRelations()->Count());
|
||||
}
|
||||
|
||||
public function testHasItemsWhenSetAsArray() {
|
||||
$tf = new TableField(
|
||||
'TestTableField',
|
||||
'TableFieldTest_HasManyRelation',
|
||||
array(
|
||||
'Value' => 'Value'
|
||||
),
|
||||
array(
|
||||
'Value' => 'TextField'
|
||||
)
|
||||
);
|
||||
$tf->setValue(array(
|
||||
'new' => array(
|
||||
'Value' => array(
|
||||
'One',
|
||||
'Two',
|
||||
)
|
||||
)
|
||||
));
|
||||
$items = $tf->Items();
|
||||
$itemsArr = $items->toArray();
|
||||
|
||||
// includes the two values and an "add" row
|
||||
$this->assertEquals($items->Count(), 3);
|
||||
|
||||
// first row
|
||||
$this->assertEquals(
|
||||
$itemsArr[0]->Fields()->fieldByName('TestTableField[new][Value][]')->Value(),
|
||||
'One'
|
||||
);
|
||||
|
||||
// second row
|
||||
$this->assertEquals(
|
||||
$itemsArr[1]->Fields()->fieldByName('TestTableField[new][Value][]')->Value(),
|
||||
'Two'
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub controller
|
||||
*/
|
||||
class TableFieldTest_Controller extends Controller implements TestOnly {
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links('TableFieldTest/', $action);
|
||||
}
|
||||
}
|
||||
class TableFieldTest_Object extends DataObject implements TestOnly {
|
||||
static $has_many = array(
|
||||
"HasManyRelations" => 'TableFieldTest_HasManyRelation'
|
||||
);
|
||||
}
|
||||
|
||||
class TableFieldTest_HasManyRelation extends DataObject implements TestOnly {
|
||||
static $db = array(
|
||||
'Value' => 'Text',
|
||||
);
|
||||
|
||||
static $has_one = array(
|
||||
'HasOneRelation' => 'TableFieldTest_Object'
|
||||
);
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
Permission:
|
||||
perm1:
|
||||
Code: Perm1
|
||||
Arg: 1
|
||||
perm2:
|
||||
Code: Perm2
|
||||
Arg: 2
|
||||
Group:
|
||||
group1_no_perms:
|
||||
Title: Group A
|
||||
group2_existing_perms:
|
||||
Title: Group B
|
||||
Permissions: =>Permission.perm1,=>Permission.perm2
|
@ -1,375 +0,0 @@
|
||||
<?php
|
||||
|
||||
class TableListFieldTest extends SapphireTest {
|
||||
static $fixture_file = 'TableListFieldTest.yml';
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'TableListFieldTest_Obj',
|
||||
'TableListFieldTest_CsvExport',
|
||||
);
|
||||
|
||||
public function testCanReferenceCustomMethodsAndFieldsOnObject() {
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$result = $table->FieldHolder();
|
||||
|
||||
// Do a quick check to ensure that some of the D() and getE() values got through
|
||||
$this->assertRegExp('/>\s*a2\s*</', $result);
|
||||
$this->assertRegExp('/>\s*a2\/b2\/c2\s*</', $result);
|
||||
$this->assertRegExp('/>\s*a2-e</', $result);
|
||||
}
|
||||
|
||||
public function testUnpaginatedSourceItemGeneration() {
|
||||
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
|
||||
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
|
||||
$item3 = $this->objFromFixture('TableListFieldTest_Obj', 'three');
|
||||
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
|
||||
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
|
||||
|
||||
// In this simple case, the source items should just list all the data objects specified
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$items = $table->sourceItems();
|
||||
$this->assertNotNull($items);
|
||||
|
||||
$itemMap = $items->map("ID", "A") ;
|
||||
$this->assertEquals(array(
|
||||
$item1->ID => "a1",
|
||||
$item2->ID => "a2",
|
||||
$item3->ID => "a3",
|
||||
$item4->ID => "a4",
|
||||
$item5->ID => "a5"
|
||||
), $itemMap->toArray());
|
||||
}
|
||||
|
||||
public function testFirstPageOfPaginatedSourceItemGeneration() {
|
||||
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
|
||||
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
|
||||
$item3 = $this->objFromFixture('TableListFieldTest_Obj', 'three');
|
||||
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
|
||||
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
|
||||
|
||||
// With pagination enabled, only the first page of items should be shown
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$table->ShowPagination = true;
|
||||
$table->PageSize = 2;
|
||||
|
||||
$items = $table->sourceItems();
|
||||
$this->assertNotNull($items);
|
||||
|
||||
$itemMap = $items->map("ID", "A") ;
|
||||
$this->assertEquals(array(
|
||||
$item1->ID => "a1",
|
||||
$item2->ID => "a2"
|
||||
), $itemMap->toArray());
|
||||
}
|
||||
|
||||
public function testSecondPageOfPaginatedSourceItemGeneration() {
|
||||
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
|
||||
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
|
||||
$item3 = $this->objFromFixture('TableListFieldTest_Obj', 'three');
|
||||
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
|
||||
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
|
||||
|
||||
// With pagination enabled, only the first page of items should be shown
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$table->ShowPagination = true;
|
||||
$table->PageSize = 2;
|
||||
$_REQUEST['ctf']['Tester']['start'] = 2;
|
||||
|
||||
$items = $table->sourceItems();
|
||||
$this->assertNotNull($items);
|
||||
|
||||
$itemMap = $items->map("ID", "A") ;
|
||||
$this->assertEquals(array($item3->ID => "a3", $item4->ID => "a4"), $itemMap->toArray());
|
||||
}
|
||||
|
||||
public function testSelectOptionsAddRemove() {
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
));
|
||||
$this->assertNull($table->SelectOptions(), 'Empty by default');
|
||||
|
||||
$table->addSelectOptions(array("F"=>"FieldF", 'G'=>'FieldG'));
|
||||
$this->assertEquals($table->SelectOptions()->map('Key', 'Value'), array("F"=>"FieldF",'G'=>'FieldG'));
|
||||
|
||||
$table->removeSelectOptions(array("F"));
|
||||
$this->assertEquals($table->SelectOptions()->map('Key', 'Value'), array("G"=>"FieldG"));
|
||||
}
|
||||
|
||||
public function testSelectOptionsRendering() {
|
||||
$obj1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
|
||||
$obj2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
|
||||
$obj3 = $this->objFromFixture('TableListFieldTest_Obj', 'three');
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
));
|
||||
$table->Markable = true;
|
||||
|
||||
$table->addSelectOptions(array("F"=>"FieldF"));
|
||||
$tableHTML = $table->FieldHolder();
|
||||
$p = new CSSContentParser($tableHTML);
|
||||
$this->assertContains('rel="F"', $tableHTML);
|
||||
$tbody = $p->getByXpath('//tbody');
|
||||
$this->assertContains('markingcheckbox F', (string)$tbody[0]->tr[0]->td[0]['class']);
|
||||
$this->assertContains('markingcheckbox', (string)$tbody[0]->tr[1]->td[0]['class']);
|
||||
$this->assertContains('markingcheckbox F', (string)$tbody[0]->tr[2]->td[0]['class']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get that visiting the field's URL returns the content of the field.
|
||||
* This capability is used by ajax
|
||||
*/
|
||||
public function testAjaxRefreshing() {
|
||||
$controller = new TableListFieldTest_TestController();
|
||||
$table = $controller->TestForm()->Fields()->First();
|
||||
|
||||
$ajaxResponse = Director::test($table->Link())->getBody();
|
||||
|
||||
// Check that the column headings have been rendered
|
||||
$this->assertRegExp('/<th[^>]*>.*Col A.*<\/th>/si', $ajaxResponse);
|
||||
$this->assertRegExp('/<th[^>]*>.*Col B.*<\/th>/si', $ajaxResponse);
|
||||
$this->assertRegExp('/<th[^>]*>.*Col C.*<\/th>/si', $ajaxResponse);
|
||||
$this->assertRegExp('/<th[^>]*>.*Col D.*<\/th>/si', $ajaxResponse);
|
||||
$this->assertRegExp('/<th[^>]*>.*Col E.*<\/th>/si', $ajaxResponse);
|
||||
}
|
||||
|
||||
public function testCsvExport() {
|
||||
$table = new TableListField("Tester", "TableListFieldTest_CsvExport", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B"
|
||||
));
|
||||
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$csvResponse = $table->export();
|
||||
|
||||
$csvOutput = $csvResponse->getBody();
|
||||
|
||||
$this->assertNotEquals($csvOutput, false);
|
||||
|
||||
// Create a temporary file and write the CSV to it.
|
||||
$csvFileName = tempnam(TEMP_FOLDER, 'csv-export');
|
||||
$csvFile = fopen($csvFileName, 'wb');
|
||||
fwrite($csvFile, $csvOutput);
|
||||
fclose($csvFile);
|
||||
|
||||
$csvFile = fopen($csvFileName, 'rb');
|
||||
$csvRow = fgetcsv($csvFile);
|
||||
$this->assertEquals(
|
||||
$csvRow,
|
||||
array('Col A', 'Col B')
|
||||
);
|
||||
|
||||
// fgetcsv doesn't handle escaped quotes in the string in PHP 5.2, so we're asserting the
|
||||
// raw string instead.
|
||||
$this->assertEquals(
|
||||
'"\"A field, with a comma\"","A second field"',
|
||||
trim(fgets($csvFile))
|
||||
);
|
||||
|
||||
fclose($csvFile);
|
||||
|
||||
unlink($csvFileName);
|
||||
}
|
||||
|
||||
public function testLink() {
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
))
|
||||
), new FieldList());
|
||||
|
||||
$table = $form->Fields()->dataFieldByName('Tester');
|
||||
$this->assertEquals(
|
||||
$table->Link('test'),
|
||||
sprintf('TableListFieldTest_TestController/TestForm/field/Tester/test?SecurityID=%s',
|
||||
$form->Fields()->dataFieldByName('SecurityID')->Value())
|
||||
);
|
||||
}
|
||||
|
||||
public function testPreservedSortOptionsInPaginationLink() {
|
||||
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
|
||||
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
|
||||
$item3 = $this->objFromFixture('TableListFieldTest_Obj', 'three');
|
||||
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
|
||||
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
|
||||
|
||||
/* With pagination enabled, only the first page of items should be shown */
|
||||
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
|
||||
$table->ShowPagination = true;
|
||||
$table->PageSize = 2;
|
||||
|
||||
// first page & sort A column by ASC
|
||||
$_REQUEST['ctf']['Tester']['start'] = 0;
|
||||
$_REQUEST['ctf']['Tester']['sort'] = 'A';
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->NextLink());
|
||||
$this->assertNotContains('ctf[Tester][dir]', $table->NextLink());
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->LastLink());
|
||||
$this->assertNotContains('ctf[Tester][dir]', $table->LastLink());
|
||||
|
||||
// second page & sort A column by ASC
|
||||
$_REQUEST['ctf']['Tester']['start'] = 2;
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->PrevLink());
|
||||
$this->assertNotContains('&ctf[Tester][dir]', $table->PrevLink());
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->FirstLink());
|
||||
$this->assertNotContains('&ctf[Tester][dir]', $table->FirstLink());
|
||||
|
||||
// first page & sort A column by DESC
|
||||
$_REQUEST['ctf']['Tester']['start'] = 0;
|
||||
$_REQUEST['ctf']['Tester']['sort'] = 'A';
|
||||
$_REQUEST['ctf']['Tester']['dir'] = 'desc';
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->NextLink());
|
||||
$this->assertContains('&ctf[Tester][dir]=desc', $table->NextLink());
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->LastLink());
|
||||
$this->assertContains('&ctf[Tester][dir]=desc', $table->LastLink());
|
||||
|
||||
// second page & sort A column by DESC
|
||||
$_REQUEST['ctf']['Tester']['start'] = 2;
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->PrevLink());
|
||||
$this->assertContains('&ctf[Tester][dir]=desc', $table->PrevLink());
|
||||
$this->assertContains('&ctf[Tester][sort]=A', $table->FirstLink());
|
||||
$this->assertContains('&ctf[Tester][dir]=desc', $table->FirstLink());
|
||||
|
||||
unset($_REQUEST['ctf']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a SS_List can be passed to TableListField
|
||||
*/
|
||||
public 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 ArrayList(array($one, $two, $three));
|
||||
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldList(
|
||||
new TableListField("Tester", $list, array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
))
|
||||
), new FieldList());
|
||||
|
||||
$table = $form->Fields()->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 {
|
||||
static $db = array(
|
||||
"A" => "Varchar",
|
||||
"B" => "Varchar",
|
||||
"C" => "Varchar",
|
||||
"F" => "Boolean",
|
||||
);
|
||||
static $default_sort = "A";
|
||||
|
||||
public function D() {
|
||||
return $this->A . '/' . $this->B . '/' . $this->C;
|
||||
}
|
||||
|
||||
public function getE() {
|
||||
return $this->A . '-e';
|
||||
}
|
||||
}
|
||||
|
||||
class TableListFieldTest_CsvExport extends DataObject implements TestOnly {
|
||||
static $db = array(
|
||||
"A" => "Varchar",
|
||||
"B" => "Varchar"
|
||||
);
|
||||
static $default_sort = "A";
|
||||
}
|
||||
|
||||
class TableListFieldTest_TestController extends Controller {
|
||||
public function Link($action = null) {
|
||||
return Controller::join_links("TableListFieldTest_TestController/", $action);
|
||||
}
|
||||
public function TestForm() {
|
||||
$table = new TableListField("Table", "TableListFieldTest_Obj", array(
|
||||
"A" => "Col A",
|
||||
"B" => "Col B",
|
||||
"C" => "Col C",
|
||||
"D" => "Col D",
|
||||
"E" => "Col E",
|
||||
));
|
||||
$table->disableSorting();
|
||||
|
||||
// A TableListField must be inside a form for its links to be generated
|
||||
return new Form($this, "TestForm", new FieldList(
|
||||
$table
|
||||
), new FieldList());
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
TableListFieldTest_Obj:
|
||||
one:
|
||||
A: a1
|
||||
B: b1
|
||||
C: c1
|
||||
F: true
|
||||
two:
|
||||
A: a2
|
||||
B: b2
|
||||
C: c2
|
||||
F: false
|
||||
three:
|
||||
A: a3
|
||||
B: b3
|
||||
C: c3
|
||||
F: true
|
||||
four:
|
||||
A: a4
|
||||
B: b4
|
||||
C: c4
|
||||
D: false
|
||||
five:
|
||||
A: a5
|
||||
B: b5
|
||||
C: c5
|
||||
F: true
|
||||
|
||||
TableListFieldTest_CsvExport:
|
||||
exportone:
|
||||
A: "\"A field, with a comma\""
|
||||
B: A second field
|
101
thirdparty/scriptaculous/builder.js
vendored
101
thirdparty/scriptaculous/builder.js
vendored
@ -1,101 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
var Builder = {
|
||||
NODEMAP: {
|
||||
AREA: 'map',
|
||||
CAPTION: 'table',
|
||||
COL: 'table',
|
||||
COLGROUP: 'table',
|
||||
LEGEND: 'fieldset',
|
||||
OPTGROUP: 'select',
|
||||
OPTION: 'select',
|
||||
PARAM: 'object',
|
||||
TBODY: 'table',
|
||||
TD: 'table',
|
||||
TFOOT: 'table',
|
||||
TH: 'table',
|
||||
THEAD: 'table',
|
||||
TR: 'table'
|
||||
},
|
||||
// note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
|
||||
// due to a Firefox bug
|
||||
node: function(elementName) {
|
||||
elementName = elementName.toUpperCase();
|
||||
|
||||
// try innerHTML approach
|
||||
var parentTag = this.NODEMAP[elementName] || 'div';
|
||||
var parentElement = document.createElement(parentTag);
|
||||
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
|
||||
parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
|
||||
} catch(e) {}
|
||||
var element = parentElement.firstChild || null;
|
||||
|
||||
// see if browser added wrapping tags
|
||||
if(element && (element.tagName != elementName))
|
||||
element = element.getElementsByTagName(elementName)[0];
|
||||
|
||||
// fallback to createElement approach
|
||||
if(!element) element = document.createElement(elementName);
|
||||
|
||||
// abort if nothing could be created
|
||||
if(!element) return;
|
||||
|
||||
// attributes (or text)
|
||||
if(arguments[1])
|
||||
if(this._isStringOrNumber(arguments[1]) ||
|
||||
(arguments[1] instanceof Array)) {
|
||||
this._children(element, arguments[1]);
|
||||
} else {
|
||||
var attrs = this._attributes(arguments[1]);
|
||||
if(attrs.length) {
|
||||
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
|
||||
parentElement.innerHTML = "<" +elementName + " " +
|
||||
attrs + "></" + elementName + ">";
|
||||
} catch(e) {}
|
||||
element = parentElement.firstChild || null;
|
||||
// workaround firefox 1.0.X bug
|
||||
if(!element) {
|
||||
element = document.createElement(elementName);
|
||||
for(attr in arguments[1])
|
||||
element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
|
||||
}
|
||||
if(element.tagName != elementName)
|
||||
element = parentElement.getElementsByTagName(elementName)[0];
|
||||
}
|
||||
}
|
||||
|
||||
// text, or array of children
|
||||
if(arguments[2])
|
||||
this._children(element, arguments[2]);
|
||||
|
||||
return element;
|
||||
},
|
||||
_text: function(text) {
|
||||
return document.createTextNode(text);
|
||||
},
|
||||
_attributes: function(attributes) {
|
||||
var attrs = [];
|
||||
for(attribute in attributes)
|
||||
attrs.push((attribute=='className' ? 'class' : attribute) +
|
||||
'="' + attributes[attribute].toString().escapeHTML() + '"');
|
||||
return attrs.join(" ");
|
||||
},
|
||||
_children: function(element, children) {
|
||||
if(typeof children=='object') { // array can hold nodes and text
|
||||
children.flatten().each( function(e) {
|
||||
if(typeof e=='object')
|
||||
element.appendChild(e)
|
||||
else
|
||||
if(Builder._isStringOrNumber(e))
|
||||
element.appendChild(Builder._text(e));
|
||||
});
|
||||
} else
|
||||
if(Builder._isStringOrNumber(children))
|
||||
element.appendChild(Builder._text(children));
|
||||
},
|
||||
_isStringOrNumber: function(param) {
|
||||
return(typeof param=='string' || typeof param=='number');
|
||||
}
|
||||
}
|
750
thirdparty/scriptaculous/controls.js
vendored
750
thirdparty/scriptaculous/controls.js
vendored
@ -1,750 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
var Autocompleter = {}
|
||||
Autocompleter.Base = function() {};
|
||||
Autocompleter.Base.prototype = {
|
||||
baseInitialize: function(element, update, options) {
|
||||
this.element = $(element);
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
|
||||
if (this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || {};
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if (typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(navigator.appVersion.indexOf('MSIE')>0) &&
|
||||
(navigator.userAgent.indexOf('Opera')<0) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix);
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.stopIndicator();
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
|
||||
return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--
|
||||
else this.index = this.entryCount-1;
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
var lastTokenPos = this.findLastToken();
|
||||
if (lastTokenPos != -1) {
|
||||
var newValue = this.element.value.substr(0, lastTokenPos + 1);
|
||||
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value;
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.element.focus();
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.firstChild);
|
||||
|
||||
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
||||
this.entryCount =
|
||||
this.update.firstChild.childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
|
||||
this.index = 0;
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.startIndicator();
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var tokenPos = this.findLastToken();
|
||||
if (tokenPos != -1)
|
||||
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
|
||||
else
|
||||
var ret = this.element.value;
|
||||
|
||||
return /\n/.test(ret) ? '' : ret;
|
||||
},
|
||||
|
||||
findLastToken: function() {
|
||||
var lastTokenPos = -1;
|
||||
|
||||
for (var i=0; i<this.options.tokens.length; i++) {
|
||||
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
||||
if (thisTokenPos > lastTokenPos)
|
||||
lastTokenPos = thisTokenPos;
|
||||
}
|
||||
return lastTokenPos;
|
||||
}
|
||||
}
|
||||
|
||||
Ajax.Autocompleter = Class.create();
|
||||
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create();
|
||||
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || {});
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor
|
||||
//
|
||||
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
|
||||
|
||||
// Use this if you notice weird scrolling problems on some browsers,
|
||||
// the DOM might be a bit confused when this gets called so do this
|
||||
// waits 1 ms (with setTimeout) until it does the activation
|
||||
Field.scrollFreeActivate = function(field) {
|
||||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
Ajax.InPlaceEditor = Class.create();
|
||||
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
|
||||
Ajax.InPlaceEditor.prototype = {
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = $(element);
|
||||
|
||||
this.options = Object.extend({
|
||||
okText: "ok",
|
||||
cancelText: "cancel",
|
||||
savingText: "Saving...",
|
||||
clickToEditText: "Click to edit",
|
||||
okText: "ok",
|
||||
rows: 1,
|
||||
onComplete: function(transport, element) {
|
||||
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
alert("Error communicating with the server: " + transport.responseText.stripTags());
|
||||
},
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
handleLineBreaks: true,
|
||||
loadingText: 'Loading...',
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
formClassName: 'inplaceeditor-form',
|
||||
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
|
||||
highlightendcolor: "#FFFFFF",
|
||||
externalControl: null,
|
||||
ajaxOptions: {}
|
||||
}, options || {});
|
||||
|
||||
if(!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + "-inplaceeditor";
|
||||
if ($(this.options.formId)) {
|
||||
// there's already a form with that name, don't specify an id
|
||||
this.options.formId = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.externalControl) {
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
}
|
||||
|
||||
this.originalBackground = Element.getStyle(this.element, 'background-color');
|
||||
if (!this.originalBackground) {
|
||||
this.originalBackground = "transparent";
|
||||
}
|
||||
|
||||
this.element.title = this.options.clickToEditText;
|
||||
|
||||
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
|
||||
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
|
||||
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
|
||||
Event.observe(this.element, 'click', this.onclickListener);
|
||||
Event.observe(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.observe(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
},
|
||||
enterEditMode: function(evt) {
|
||||
if (this.saving) return;
|
||||
if (this.editing) return;
|
||||
this.editing = true;
|
||||
this.onEnterEditMode();
|
||||
if (this.options.externalControl) {
|
||||
Element.hide(this.options.externalControl);
|
||||
}
|
||||
Element.hide(this.element);
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this.form, this.element);
|
||||
Field.scrollFreeActivate(this.editField);
|
||||
// stop the event to avoid a page refresh in Safari
|
||||
if (evt) {
|
||||
Event.stop(evt);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
createForm: function() {
|
||||
this.form = document.createElement("form");
|
||||
this.form.id = this.options.formId;
|
||||
Element.addClassName(this.form, this.options.formClassName)
|
||||
this.form.onsubmit = this.onSubmit.bind(this);
|
||||
|
||||
this.createEditField();
|
||||
|
||||
if (this.options.textarea) {
|
||||
var br = document.createElement("br");
|
||||
this.form.appendChild(br);
|
||||
}
|
||||
|
||||
okButton = document.createElement("input");
|
||||
okButton.type = "submit";
|
||||
okButton.value = this.options.okText;
|
||||
this.form.appendChild(okButton);
|
||||
|
||||
cancelLink = document.createElement("a");
|
||||
cancelLink.href = "#";
|
||||
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
|
||||
cancelLink.onclick = this.onclickCancel.bind(this);
|
||||
this.form.appendChild(cancelLink);
|
||||
},
|
||||
hasHTMLLineBreaks: function(string) {
|
||||
if (!this.options.handleLineBreaks) return false;
|
||||
return string.match(/<br/i) || string.match(/<p>/i);
|
||||
},
|
||||
convertHTMLLineBreaks: function(string) {
|
||||
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
|
||||
},
|
||||
createEditField: function() {
|
||||
var text;
|
||||
if(this.options.loadTextURL) {
|
||||
text = this.options.loadingText;
|
||||
} else {
|
||||
text = this.getText();
|
||||
}
|
||||
|
||||
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
|
||||
this.options.textarea = false;
|
||||
var textField = document.createElement("input");
|
||||
textField.type = "text";
|
||||
textField.name = "value";
|
||||
textField.value = text;
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (size != 0) textField.size = size;
|
||||
this.editField = textField;
|
||||
} else {
|
||||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.name = "value";
|
||||
textArea.value = this.convertHTMLLineBreaks(text);
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
this.editField = textArea;
|
||||
}
|
||||
|
||||
if(this.options.loadTextURL) {
|
||||
this.loadExternalText();
|
||||
}
|
||||
this.form.appendChild(this.editField);
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML;
|
||||
},
|
||||
loadExternalText: function() {
|
||||
Element.addClassName(this.form, this.options.loadingClassName);
|
||||
this.editField.disabled = true;
|
||||
new Ajax.Request(
|
||||
this.options.loadTextURL,
|
||||
Object.extend({
|
||||
asynchronous: true,
|
||||
onComplete: this.onLoadedExternalText.bind(this)
|
||||
}, this.options.ajaxOptions)
|
||||
);
|
||||
},
|
||||
onLoadedExternalText: function(transport) {
|
||||
Element.removeClassName(this.form, this.options.loadingClassName);
|
||||
this.editField.disabled = false;
|
||||
this.editField.value = transport.responseText.stripTags();
|
||||
},
|
||||
onclickCancel: function() {
|
||||
this.onComplete();
|
||||
this.leaveEditMode();
|
||||
return false;
|
||||
},
|
||||
onFailure: function(transport) {
|
||||
this.options.onFailure(transport);
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
this.oldInnerHTML = null;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onSubmit: function() {
|
||||
// onLoading resets these so we need to save them away for the Ajax call
|
||||
var form = this.form;
|
||||
var value = this.editField.value;
|
||||
|
||||
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
|
||||
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
|
||||
// to be displayed indefinitely
|
||||
this.onLoading();
|
||||
|
||||
new Ajax.Updater(
|
||||
{
|
||||
success: this.element,
|
||||
// don't update on failure (this could be an option)
|
||||
failure: null
|
||||
},
|
||||
this.url,
|
||||
Object.extend({
|
||||
parameters: this.options.callback(form, value),
|
||||
onComplete: this.onComplete.bind(this),
|
||||
onFailure: this.onFailure.bind(this)
|
||||
}, this.options.ajaxOptions)
|
||||
);
|
||||
// stop the event to avoid a page refresh in Safari
|
||||
if (arguments.length > 1) {
|
||||
Event.stop(arguments[0]);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onLoading: function() {
|
||||
this.saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
showSaving: function() {
|
||||
this.oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
Element.addClassName(this.element, this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
},
|
||||
removeForm: function() {
|
||||
if(this.form) {
|
||||
if (this.form.parentNode) Element.remove(this.form);
|
||||
this.form = null;
|
||||
}
|
||||
},
|
||||
enterHover: function() {
|
||||
if (this.saving) return;
|
||||
this.element.style.backgroundColor = this.options.highlightcolor;
|
||||
if (this.effect) {
|
||||
this.effect.cancel();
|
||||
}
|
||||
Element.addClassName(this.element, this.options.hoverClassName)
|
||||
},
|
||||
leaveHover: function() {
|
||||
if (this.options.backgroundColor) {
|
||||
this.element.style.backgroundColor = this.oldBackground;
|
||||
}
|
||||
Element.removeClassName(this.element, this.options.hoverClassName)
|
||||
if (this.saving) return;
|
||||
this.effect = new Effect.Highlight(this.element, {
|
||||
startcolor: this.options.highlightcolor,
|
||||
endcolor: this.options.highlightendcolor,
|
||||
restorecolor: this.originalBackground
|
||||
});
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
Element.removeClassName(this.element, this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this.originalBackground;
|
||||
Element.show(this.element);
|
||||
if (this.options.externalControl) {
|
||||
Element.show(this.options.externalControl);
|
||||
}
|
||||
this.editing = false;
|
||||
this.saving = false;
|
||||
this.oldInnerHTML = null;
|
||||
this.onLeaveEditMode();
|
||||
},
|
||||
onComplete: function(transport) {
|
||||
this.leaveEditMode();
|
||||
this.options.onComplete.bind(this)(transport, this.element);
|
||||
},
|
||||
onEnterEditMode: function() {},
|
||||
onLeaveEditMode: function() {},
|
||||
dispose: function() {
|
||||
if (this.oldInnerHTML) {
|
||||
this.element.innerHTML = this.oldInnerHTML;
|
||||
}
|
||||
this.leaveEditMode();
|
||||
Event.stopObserving(this.element, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
|
||||
if (this.options.externalControl) {
|
||||
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
|
||||
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create();
|
||||
Form.Element.DelayedObserver.prototype = {
|
||||
initialize: function(element, delay, callback) {
|
||||
this.delay = delay || 0.5;
|
||||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
if(this.lastValue == $F(this.element)) return;
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
||||
this.lastValue = $F(this.element);
|
||||
},
|
||||
onTimerEvent: function() {
|
||||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
};
|
581
thirdparty/scriptaculous/dragdrop.js
vendored
581
thirdparty/scriptaculous/dragdrop.js
vendored
@ -1,581 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
||||
remove: function(element) {
|
||||
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null
|
||||
}, arguments[1] || {});
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = [];
|
||||
var containment = options.containment;
|
||||
if((typeof containment == 'object') &&
|
||||
(containment.constructor == Array)) {
|
||||
containment.each( function(c) { options._containers.push($(c)) });
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
// Element.makePositioned(element); // fix IE
|
||||
options.element = element;
|
||||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
isContained: function(element, drop) {
|
||||
var parentNode = element.parentNode;
|
||||
return drop._containers.detect(function(c) { return parentNode == c });
|
||||
},
|
||||
|
||||
isAffected: function(pX, pY, element, drop) {
|
||||
return (
|
||||
(drop.element!=element) && (drop.element.parentNode) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
((!drop.checkDroppableIsntContained) || !Element.contains(element, drop.element)) &&
|
||||
Position.within(drop.element, pX, pY) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.removeClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(this.last_active) this.deactivate(this.last_active);
|
||||
if(drop.hoverclass)
|
||||
Element.addClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(event, element) {
|
||||
if(!this.drops.length) return;
|
||||
var pX = Event.pointerX(event);
|
||||
var pY = Event.pointerY(event);
|
||||
Position.prepare();
|
||||
|
||||
var i = this.drops.length-1; do {
|
||||
var drop = this.drops[i];
|
||||
if(this.isAffected(pX, pY, element, drop)) {
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
if(drop.greedy) {
|
||||
this.activate(drop);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while (i--);
|
||||
|
||||
if(this.last_active) this.deactivate(this.last_active);
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
|
||||
if (this.last_active.onDrop)
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
}
|
||||
|
||||
var Draggables = {
|
||||
observers: [],
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
},
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
function(o) { return o[eventName]; }
|
||||
).length;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create();
|
||||
Draggable.prototype = {
|
||||
initialize: function(element) {
|
||||
var options = Object.extend({
|
||||
handle: false,
|
||||
starteffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
||||
},
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
}, arguments[1] || {});
|
||||
|
||||
this.element = $(element);
|
||||
if(options.handle && (typeof options.handle == 'string'))
|
||||
this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
this.originalLeft = this.currentLeft();
|
||||
this.originalTop = this.currentTop();
|
||||
this.originalX = this.element.offsetLeft;
|
||||
this.originalY = this.element.offsetTop;
|
||||
|
||||
this.options = options;
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.update.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
this.registerEvents();
|
||||
},
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
this.unregisterEvents();
|
||||
},
|
||||
registerEvents: function() {
|
||||
//if(!Draggable.eventsRegistered) {
|
||||
//Draggable.eventsRegistered = true;
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
//}
|
||||
},
|
||||
unregisterEvents: function() {
|
||||
//if(!Draggable.eventsRegistered) {
|
||||
//Draggable.eventsRegistered = false;
|
||||
//if(!this.active) return;
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
//}
|
||||
},
|
||||
currentLeft: function() {
|
||||
return parseInt(this.element.style.left || '0');
|
||||
},
|
||||
currentTop: function() {
|
||||
return parseInt(this.element.style.top || '0')
|
||||
},
|
||||
startDrag: function(event) {
|
||||
if(Event.isLeftClick(event)) {
|
||||
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if(src.tagName && (
|
||||
src.tagName=='INPUT' ||
|
||||
src.tagName=='SELECT' ||
|
||||
src.tagName=='BUTTON' ||
|
||||
src.tagName=='TEXTAREA')) return;
|
||||
|
||||
// this.registerEvents();
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.element.wasDragged = false;
|
||||
|
||||
this.active = true;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
this.offsetX = (pointer[0] - offsets[0]);
|
||||
this.offsetY = (pointer[1] - offsets[1]);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
finishDrag: function(event, success) {
|
||||
// this.unregisterEvents();
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
this.element.wasDragged = true;
|
||||
|
||||
if(this.options.ghosting) {
|
||||
Position.relativize(this.element);
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
if(success) Droppables.fire(event, this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && typeof revert == 'function') revert = revert(this.element);
|
||||
|
||||
if(revert && this.options.reverteffect) {
|
||||
this.options.reverteffect(this.element,
|
||||
this.currentTop()-this.originalTop,
|
||||
this.currentLeft()-this.originalLeft);
|
||||
} else {
|
||||
this.originalLeft = this.currentLeft();
|
||||
this.originalTop = this.currentTop();
|
||||
}
|
||||
|
||||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
|
||||
Droppables.reset();
|
||||
},
|
||||
keyPress: function(event) {
|
||||
if(this.active) {
|
||||
if(event.keyCode==Event.KEY_ESC) {
|
||||
Element.undoPositioned(this.element);
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
endDrag: function(event) {
|
||||
Element.undoPositioned(this.element);
|
||||
if(this.active && this.dragging) {
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
}
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
},
|
||||
draw: function(event) {
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
offsets[0] -= this.currentLeft();
|
||||
offsets[1] -= this.currentTop();
|
||||
var style = this.element.style;
|
||||
|
||||
var pos = [
|
||||
(pointer[0] - offsets[0] - this.offsetX),
|
||||
(pointer[1] - offsets[1] - this.offsetY)];
|
||||
|
||||
if(this.options.snap) {
|
||||
if(typeof this.options.snap == 'function') {
|
||||
pos = this.options.snap(pos[0],pos[1]);
|
||||
} else {
|
||||
var draggable = this;
|
||||
if(this.options.snap instanceof Array) {
|
||||
pos = pos.collect( function(v, i) {
|
||||
return Math.round(v/draggable.options.snap[i])*draggable.options.snap[i] })
|
||||
} else {
|
||||
pos = pos.collect( function(v) {
|
||||
return Math.round(v/draggable.options.snap)*draggable.options.snap })
|
||||
}
|
||||
}}
|
||||
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = pos[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = pos[1] + "px";
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
update: function(event) {
|
||||
if(this.active) {
|
||||
if(!this.dragging) {
|
||||
if(this.options.onStartDrag) this.options.onStartDrag(this.element);
|
||||
|
||||
var style = this.element.style;
|
||||
this.dragging = true;
|
||||
|
||||
if(Element.getStyle(this.element,'position')=='')
|
||||
style.position = "relative";
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
}
|
||||
|
||||
Droppables.show(event, this.element);
|
||||
Draggables.notify('onDrag', this, event);
|
||||
this.draw(event);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rollingStatus(chr, maxLength) {
|
||||
var txt = window.status + chr;
|
||||
if(txt.length > maxLength) txt = txt.substr(txt.length - maxLength);
|
||||
window.status = txt;
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create();
|
||||
SortableObserver.prototype = {
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
}
|
||||
|
||||
var Sortable = {
|
||||
sortables: new Array(),
|
||||
options: function(element){
|
||||
element = $(element);
|
||||
return this.sortables.detect(function(s) { return s.element == element });
|
||||
},
|
||||
destroy: function(element){
|
||||
element = $(element);
|
||||
this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
});
|
||||
this.sortables = this.sortables.reject(function(s) { return s.element == element });
|
||||
},
|
||||
create: function(element) {
|
||||
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
element: element,
|
||||
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||
dropOnEmpty: false,
|
||||
tree: false, // fixme: unimplemented
|
||||
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||
containment: element, // also takes array of elements (or id's); or false
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
format: null,
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || {});
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover,
|
||||
greedy: !options.dropOnEmpty
|
||||
}
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// make it so
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty) {
|
||||
Droppables.add(element,
|
||||
{containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
(this.findElements(element, options) || []).each( function(e) {
|
||||
// handles are per-draggable
|
||||
var handle = options.handle ?
|
||||
Element.childrenWithClassName(e, options.handle)[0] : e;
|
||||
options.draggables.push(
|
||||
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||
Droppables.add(e, options_for_droppable);
|
||||
options.droppables.push(e);
|
||||
});
|
||||
|
||||
// keep reference
|
||||
this.sortables.push(options);
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
if(!element.hasChildNodes()) return null;
|
||||
var elements = [];
|
||||
$A(element.childNodes).each( function(e) {
|
||||
if(e.tagName && e.tagName==options.tag.toUpperCase() &&
|
||||
(!options.only || (Element.hasClassName(e, options.only))))
|
||||
elements.push(e);
|
||||
if(options.tree) {
|
||||
var grandchildren = this.findElements(e, options);
|
||||
if(grandchildren) elements.push(grandchildren);
|
||||
}
|
||||
});
|
||||
|
||||
return (elements.length>0 ? elements.flatten() : null);
|
||||
},
|
||||
|
||||
onHover: function(element, dropon, overlap) {
|
||||
if(overlap>0.5) {
|
||||
Sortable.mark(dropon, 'before');
|
||||
if(dropon.previousSibling != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onEmptyHover: function(element, dropon) {
|
||||
if(element.parentNode!=dropon) {
|
||||
var oldParentNode = element.parentNode;
|
||||
dropon.appendChild(element);
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon).onChange(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Element.hide(Sortable._marker);
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
||||
Element.hide(Sortable._marker);
|
||||
Element.addClassName(Sortable._marker, 'dropmarker');
|
||||
Sortable._marker.style.position = 'absolute';
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.style.left = offsets[0] + 'px';
|
||||
Sortable._marker.style.top = offsets[1] + 'px';
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
|
||||
else
|
||||
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
||||
|
||||
Element.show(Sortable._marker);
|
||||
},
|
||||
|
||||
serialize: function(element) {
|
||||
element = $(element);
|
||||
var sortableOptions = this.options(element);
|
||||
var options = Object.extend({
|
||||
tag: sortableOptions.tag,
|
||||
only: sortableOptions.only,
|
||||
name: element.id,
|
||||
format: sortableOptions.format || /^[^_]*_(.*)$/
|
||||
}, arguments[1] || {});
|
||||
var ret = $(this.findElements(element, options) || []).collect( function(item) {
|
||||
return (encodeURIComponent(options.name) + "[]=" +
|
||||
encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
|
||||
}).join("&");
|
||||
return ret;
|
||||
}
|
||||
}
|
904
thirdparty/scriptaculous/effects.js
vendored
904
thirdparty/scriptaculous/effects.js
vendored
@ -1,904 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Contributors:
|
||||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
// Martin Bialasinki
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
/* ------------- element ext -------------- */
|
||||
|
||||
// converts rgb() and #xxx to #xxxxxx format,
|
||||
// returns self (or first argument) if not convertable
|
||||
String.prototype.parseColor = function() {
|
||||
color = "#";
|
||||
if(this.slice(0,4) == "rgb(") {
|
||||
var cols = this.slice(4,this.length-1).split(',');
|
||||
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
|
||||
} else {
|
||||
if(this.slice(0,1) == '#') {
|
||||
if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
|
||||
if(this.length==7) color = this.toLowerCase();
|
||||
}
|
||||
}
|
||||
return(color.length==7 ? color : (arguments[0] || this));
|
||||
}
|
||||
|
||||
Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
|
||||
var children = $(element).childNodes;
|
||||
var text = "";
|
||||
var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
|
||||
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
if(children[i].nodeType==3) {
|
||||
text+=children[i].nodeValue;
|
||||
} else {
|
||||
if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
|
||||
text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
Element.setContentZoom = function(element, percent) {
|
||||
element = $(element);
|
||||
element.style.fontSize = (percent/100) + "em";
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
}
|
||||
|
||||
Element.getOpacity = function(element){
|
||||
var opacity;
|
||||
if (opacity = Element.getStyle(element, "opacity"))
|
||||
return parseFloat(opacity);
|
||||
if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/))
|
||||
if(opacity[1]) return parseFloat(opacity[1]) / 100;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Element.setOpacity = function(element, value){
|
||||
element= $(element);
|
||||
var els = element.style;
|
||||
if (value == 1){
|
||||
els.opacity = '0.999999';
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'');
|
||||
} else {
|
||||
if(value < 0.00001) value = 0;
|
||||
els.opacity = value;
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
|
||||
"alpha(opacity="+value*100+")";
|
||||
}
|
||||
}
|
||||
|
||||
Element.getInlineOpacity = function(element){
|
||||
element= $(element);
|
||||
var op;
|
||||
op = element.style.opacity;
|
||||
if (typeof op != "undefined" && op != "") return op;
|
||||
return "";
|
||||
}
|
||||
|
||||
Element.setInlineOpacity = function(element, value){
|
||||
element= $(element);
|
||||
var els = element.style;
|
||||
els.opacity = value;
|
||||
}
|
||||
|
||||
Element.childrenWithClassName = function(element, className) {
|
||||
return $A($(element).getElementsByTagName('*')).select(
|
||||
function(c) { return Element.hasClassName(c, className) });
|
||||
}
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Effect = {
|
||||
tagifyText: function(element) {
|
||||
var tagifyStyle = "position:relative";
|
||||
if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
|
||||
element = $(element);
|
||||
$A(element.childNodes).each( function(child) {
|
||||
if(child.nodeType==3) {
|
||||
child.nodeValue.toArray().each( function(character) {
|
||||
element.insertBefore(
|
||||
Builder.node('span',{style: tagifyStyle},
|
||||
character == " " ? String.fromCharCode(160) : character),
|
||||
child);
|
||||
});
|
||||
Element.remove(child);
|
||||
}
|
||||
});
|
||||
},
|
||||
multiple: function(element, effect) {
|
||||
var elements;
|
||||
if(((typeof element == 'object') ||
|
||||
(typeof element == 'function')) &&
|
||||
(element.length))
|
||||
elements = element;
|
||||
else
|
||||
elements = $(element).childNodes;
|
||||
|
||||
var options = Object.extend({
|
||||
speed: 0.1,
|
||||
delay: 0.0
|
||||
}, arguments[2] || {});
|
||||
var speed = options.speed;
|
||||
var delay = options.delay;
|
||||
|
||||
$A(elements).each( function(element, index) {
|
||||
new effect(element, Object.extend(options, { delay: delay + index * speed }));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var Effect2 = Effect; // deprecated
|
||||
|
||||
/* ------------- transitions ------------- */
|
||||
|
||||
Effect.Transitions = {}
|
||||
|
||||
Effect.Transitions.linear = function(pos) {
|
||||
return pos;
|
||||
}
|
||||
Effect.Transitions.sinoidal = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.reverse = function(pos) {
|
||||
return 1-pos;
|
||||
}
|
||||
Effect.Transitions.flicker = function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
}
|
||||
Effect.Transitions.wobble = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.pulse = function(pos) {
|
||||
return (Math.floor(pos*10) % 2 == 0 ?
|
||||
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
|
||||
}
|
||||
Effect.Transitions.none = function(pos) {
|
||||
return 0;
|
||||
}
|
||||
Effect.Transitions.full = function(pos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ------------- core effects ------------- */
|
||||
|
||||
Effect.Queue = {
|
||||
effects: [],
|
||||
_each: function(iterator) {
|
||||
this.effects._each(iterator);
|
||||
},
|
||||
interval: null,
|
||||
add: function(effect) {
|
||||
var timestamp = new Date().getTime();
|
||||
|
||||
switch(effect.options.queue) {
|
||||
case 'front':
|
||||
// move unstarted effects after this effect
|
||||
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
|
||||
e.startOn += effect.finishOn;
|
||||
e.finishOn += effect.finishOn;
|
||||
});
|
||||
break;
|
||||
case 'end':
|
||||
// start effect after last queued effect has finished
|
||||
timestamp = this.effects.pluck('finishOn').max() || timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
effect.startOn += timestamp;
|
||||
effect.finishOn += timestamp;
|
||||
this.effects.push(effect);
|
||||
if(!this.interval)
|
||||
this.interval = setInterval(this.loop.bind(this), 40);
|
||||
},
|
||||
remove: function(effect) {
|
||||
this.effects = this.effects.reject(function(e) { return e==effect });
|
||||
if(this.effects.length == 0) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
},
|
||||
loop: function() {
|
||||
var timePos = new Date().getTime();
|
||||
this.effects.invoke('loop', timePos);
|
||||
}
|
||||
}
|
||||
Object.extend(Effect.Queue, Enumerable);
|
||||
|
||||
Effect.Base = function() {};
|
||||
Effect.Base.prototype = {
|
||||
position: null,
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
transition: Effect.Transitions.sinoidal,
|
||||
duration: 1.0, // seconds
|
||||
fps: 25.0, // max. 25fps due to Effect.Queue implementation
|
||||
sync: false, // true for combining
|
||||
from: 0.0,
|
||||
to: 1.0,
|
||||
delay: 0.0,
|
||||
queue: 'parallel'
|
||||
}, options || {});
|
||||
},
|
||||
start: function(options) {
|
||||
this.setOptions(options || {});
|
||||
this.currentFrame = 0;
|
||||
this.state = 'idle';
|
||||
this.startOn = this.options.delay*1000;
|
||||
this.finishOn = this.startOn + (this.options.duration*1000);
|
||||
this.event('beforeStart');
|
||||
if(!this.options.sync) Effect.Queue.add(this);
|
||||
},
|
||||
loop: function(timePos) {
|
||||
if(timePos >= this.startOn) {
|
||||
if(timePos >= this.finishOn) {
|
||||
this.render(1.0);
|
||||
this.cancel();
|
||||
this.event('beforeFinish');
|
||||
if(this.finish) this.finish();
|
||||
this.event('afterFinish');
|
||||
return;
|
||||
}
|
||||
var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
|
||||
var frame = Math.round(pos * this.options.fps * this.options.duration);
|
||||
if(frame > this.currentFrame) {
|
||||
this.render(pos);
|
||||
this.currentFrame = frame;
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function(pos) {
|
||||
if(this.state == 'idle') {
|
||||
this.state = 'running';
|
||||
this.event('beforeSetup');
|
||||
if(this.setup) this.setup();
|
||||
this.event('afterSetup');
|
||||
}
|
||||
if(this.options.transition) pos = this.options.transition(pos);
|
||||
pos *= (this.options.to-this.options.from);
|
||||
pos += this.options.from;
|
||||
this.position = pos;
|
||||
this.event('beforeUpdate');
|
||||
if(this.update) this.update(pos);
|
||||
this.event('afterUpdate');
|
||||
},
|
||||
cancel: function() {
|
||||
if(!this.options.sync) Effect.Queue.remove(this);
|
||||
this.state = 'finished';
|
||||
},
|
||||
event: function(eventName) {
|
||||
if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
|
||||
if(this.options[eventName]) this.options[eventName](this);
|
||||
}
|
||||
}
|
||||
|
||||
Effect.Parallel = Class.create();
|
||||
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
|
||||
initialize: function(effects) {
|
||||
this.effects = effects || [];
|
||||
this.start(arguments[1]);
|
||||
},
|
||||
update: function(position) {
|
||||
this.effects.invoke('render', position);
|
||||
},
|
||||
finish: function(position) {
|
||||
this.effects.each( function(effect) {
|
||||
effect.render(1.0);
|
||||
effect.cancel();
|
||||
effect.event('beforeFinish');
|
||||
if(effect.finish) effect.finish(position);
|
||||
effect.event('afterFinish');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Opacity = Class.create();
|
||||
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
// make this work on IE on elements without 'layout'
|
||||
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
|
||||
this.element.style.zoom = 1;
|
||||
var options = Object.extend({
|
||||
from: Element.getOpacity(this.element) || 0.0,
|
||||
to: 1.0
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
update: function(position) {
|
||||
Element.setOpacity(this.element, position);
|
||||
}
|
||||
});
|
||||
|
||||
Effect.MoveBy = Class.create();
|
||||
Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, toTop, toLeft) {
|
||||
this.element = $(element);
|
||||
this.toTop = toTop;
|
||||
this.toLeft = toLeft;
|
||||
this.start(arguments[3]);
|
||||
},
|
||||
setup: function() {
|
||||
// Bug in Opera: Opera returns the "real" position of a static element or
|
||||
// relative element that does not have top/left explicitly set.
|
||||
// ==> Always set top and left for position relative elements in your stylesheets
|
||||
// (to 0 if you do not need them)
|
||||
|
||||
Element.makePositioned(this.element);
|
||||
this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
|
||||
this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
|
||||
},
|
||||
update: function(position) {
|
||||
var topd = this.toTop * position + this.originalTop;
|
||||
var leftd = this.toLeft * position + this.originalLeft;
|
||||
this.setPosition(topd, leftd);
|
||||
},
|
||||
setPosition: function(topd, leftd) {
|
||||
this.element.style.top = topd + "px";
|
||||
this.element.style.left = leftd + "px";
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Scale = Class.create();
|
||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, percent) {
|
||||
this.element = $(element)
|
||||
var options = Object.extend({
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
scaleContent: true,
|
||||
scaleFromCenter: false,
|
||||
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
|
||||
scaleFrom: 100.0,
|
||||
scaleTo: percent
|
||||
}, arguments[2] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
var effect = this;
|
||||
|
||||
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
|
||||
this.elementPositioning = Element.getStyle(this.element,'position');
|
||||
|
||||
effect.originalStyle = {};
|
||||
['top','left','width','height','fontSize'].each( function(k) {
|
||||
effect.originalStyle[k] = effect.element.style[k];
|
||||
});
|
||||
|
||||
this.originalTop = this.element.offsetTop;
|
||||
this.originalLeft = this.element.offsetLeft;
|
||||
|
||||
var fontSize = Element.getStyle(this.element,'font-size') || "100%";
|
||||
['em','px','%'].each( function(fontSizeType) {
|
||||
if(fontSize.indexOf(fontSizeType)>0) {
|
||||
effect.fontSize = parseFloat(fontSize);
|
||||
effect.fontSizeType = fontSizeType;
|
||||
}
|
||||
});
|
||||
|
||||
this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
|
||||
|
||||
this.dims = null;
|
||||
if(this.options.scaleMode=='box')
|
||||
this.dims = [this.element.clientHeight, this.element.clientWidth];
|
||||
if(/^content/.test(this.options.scaleMode))
|
||||
this.dims = [this.element.scrollHeight, this.element.scrollWidth];
|
||||
if(!this.dims)
|
||||
this.dims = [this.options.scaleMode.originalHeight,
|
||||
this.options.scaleMode.originalWidth];
|
||||
},
|
||||
update: function(position) {
|
||||
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
|
||||
if(this.options.scaleContent && this.fontSize)
|
||||
this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
|
||||
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
|
||||
},
|
||||
finish: function(position) {
|
||||
if (this.restoreAfterFinish) {
|
||||
var effect = this;
|
||||
['top','left','width','height','fontSize'].each( function(k) {
|
||||
effect.element.style[k] = effect.originalStyle[k];
|
||||
});
|
||||
}
|
||||
},
|
||||
setDimensions: function(height, width) {
|
||||
var els = this.element.style;
|
||||
if(this.options.scaleX) els.width = width + 'px';
|
||||
if(this.options.scaleY) els.height = height + 'px';
|
||||
if(this.options.scaleFromCenter) {
|
||||
var topd = (height - this.dims[0])/2;
|
||||
var leftd = (width - this.dims[1])/2;
|
||||
if(this.elementPositioning == 'absolute') {
|
||||
if(this.options.scaleY) els.top = this.originalTop-topd + "px";
|
||||
if(this.options.scaleX) els.left = this.originalLeft-leftd + "px";
|
||||
} else {
|
||||
if(this.options.scaleY) els.top = -topd + "px";
|
||||
if(this.options.scaleX) els.left = -leftd + "px";
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Effect.Highlight = Class.create();
|
||||
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
var options = Object.extend({
|
||||
startcolor: "#ffff99"
|
||||
}, arguments[1] || {});
|
||||
this.start(options);
|
||||
},
|
||||
setup: function() {
|
||||
// Prevent executing on elements not in the layout flow
|
||||
if(this.element.style.display=='none') { this.cancel(); return; }
|
||||
// Disable background image during the effect
|
||||
this.oldBgImage = this.element.style.backgroundImage;
|
||||
this.element.style.backgroundImage = "none";
|
||||
if(!this.options.endcolor)
|
||||
this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
|
||||
if (typeof this.options.restorecolor == "undefined")
|
||||
this.options.restorecolor = this.element.style.backgroundColor;
|
||||
// init color calculations
|
||||
this.colors_base = [
|
||||
parseInt(this.options.startcolor.slice(1,3),16),
|
||||
parseInt(this.options.startcolor.slice(3,5),16),
|
||||
parseInt(this.options.startcolor.slice(5),16) ];
|
||||
this.colors_delta = [
|
||||
parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
|
||||
parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
|
||||
parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
|
||||
},
|
||||
update: function(position) {
|
||||
var effect = this; var colors = $R(0,2).map( function(i){
|
||||
return Math.round(effect.colors_base[i]+(effect.colors_delta[i]*position))
|
||||
});
|
||||
this.element.style.backgroundColor = "#" +
|
||||
colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart();
|
||||
},
|
||||
finish: function() {
|
||||
this.element.style.backgroundColor = this.options.restorecolor;
|
||||
this.element.style.backgroundImage = this.oldBgImage;
|
||||
}
|
||||
});
|
||||
|
||||
Effect.ScrollTo = Class.create();
|
||||
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
this.start(arguments[1] || {});
|
||||
},
|
||||
setup: function() {
|
||||
Position.prepare();
|
||||
var offsets = Position.cumulativeOffset(this.element);
|
||||
var max = window.innerHeight ?
|
||||
window.height - window.innerHeight :
|
||||
document.body.scrollHeight -
|
||||
(document.documentElement.clientHeight ?
|
||||
document.documentElement.clientHeight : document.body.clientHeight);
|
||||
this.scrollStart = Position.deltaY;
|
||||
this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
|
||||
},
|
||||
update: function(position) {
|
||||
Position.prepare();
|
||||
window.scrollTo(Position.deltaX,
|
||||
this.scrollStart + (position*this.delta));
|
||||
}
|
||||
});
|
||||
|
||||
/* ------------- combination effects ------------- */
|
||||
|
||||
Effect.Fade = function(element) {
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
var options = Object.extend({
|
||||
from: Element.getOpacity(element) || 1.0,
|
||||
to: 0.0,
|
||||
afterFinishInternal: function(effect)
|
||||
{ if (effect.options.to == 0) {
|
||||
Element.hide(effect.element);
|
||||
Element.setInlineOpacity(effect.element, oldOpacity);
|
||||
}
|
||||
}
|
||||
}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Appear = function(element) {
|
||||
var options = Object.extend({
|
||||
from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0),
|
||||
to: 1.0,
|
||||
beforeSetup: function(effect)
|
||||
{ Element.setOpacity(effect.element, effect.options.from);
|
||||
Element.show(effect.element); }
|
||||
}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
var oldPosition = element.style.position;
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeSetupInternal: function(effect)
|
||||
{ effect.effects[0].element.style.position = 'absolute'; },
|
||||
afterFinishInternal: function(effect)
|
||||
{ Element.hide(effect.effects[0].element);
|
||||
effect.effects[0].element.style.position = oldPosition;
|
||||
Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindUp = function(element) {
|
||||
element = $(element);
|
||||
Element.makeClipping(element);
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
restoreAfterFinish: true,
|
||||
afterFinishInternal: function(effect)
|
||||
{
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindDown = function(element) {
|
||||
element = $(element);
|
||||
var oldHeight = element.style.height;
|
||||
var elementDimensions = Element.getDimensions(element);
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
Element.makeClipping(effect.element);
|
||||
effect.element.style.height = "0px";
|
||||
Element.show(effect.element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.undoClipping(effect.element);
|
||||
effect.element.style.height = oldHeight;
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SwitchOff = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
return new Effect.Appear(element, {
|
||||
duration: 0.4,
|
||||
from: 0,
|
||||
transition: Effect.Transitions.flicker,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(effect.element, 1, {
|
||||
duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
Element.makePositioned(effect.element);
|
||||
Element.makeClipping(effect.element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
Element.undoPositioned(effect.element);
|
||||
Element.setInlineOpacity(effect.element, oldOpacity);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Effect.DropOut = function(element) {
|
||||
element = $(element);
|
||||
var oldTop = element.style.top;
|
||||
var oldLeft = element.style.left;
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.MoveBy(element, 100, 0, { sync: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
|
||||
Object.extend(
|
||||
{ duration: 0.5,
|
||||
beforeSetup: function(effect) {
|
||||
Element.makePositioned(effect.effects[0].element); },
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.hide(effect.effects[0].element);
|
||||
Element.undoPositioned(effect.effects[0].element);
|
||||
effect.effects[0].element.style.left = oldLeft;
|
||||
effect.effects[0].element.style.top = oldTop;
|
||||
Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
|
||||
}, arguments[1] || {}));
|
||||
}
|
||||
|
||||
Effect.Shake = function(element) {
|
||||
element = $(element);
|
||||
var oldTop = element.style.top;
|
||||
var oldLeft = element.style.left;
|
||||
return new Effect.MoveBy(element, 0, 20,
|
||||
{ duration: 0.05, afterFinishInternal: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -40,
|
||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, 40,
|
||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -40,
|
||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, 40,
|
||||
{ duration: 0.1, afterFinishInternal: function(effect) {
|
||||
new Effect.MoveBy(effect.element, 0, -20,
|
||||
{ duration: 0.05, afterFinishInternal: function(effect) {
|
||||
Element.undoPositioned(effect.element);
|
||||
effect.element.style.left = oldLeft;
|
||||
effect.element.style.top = oldTop;
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
element = $(element);
|
||||
Element.cleanWhitespace(element);
|
||||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||
var oldInnerBottom = element.firstChild.style.bottom;
|
||||
var elementDimensions = Element.getDimensions(element);
|
||||
return new Effect.Scale(element, 100,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
Element.makePositioned(effect.element.firstChild);
|
||||
if (window.opera) effect.element.firstChild.style.top = "";
|
||||
Element.makeClipping(effect.element);
|
||||
element.style.height = '0';
|
||||
Element.show(element);
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.style.bottom =
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px'; },
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.undoClipping(effect.element);
|
||||
Element.undoPositioned(effect.element.firstChild);
|
||||
effect.element.firstChild.style.bottom = oldInnerBottom; }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SlideUp = function(element) {
|
||||
element = $(element);
|
||||
Element.cleanWhitespace(element);
|
||||
var oldInnerBottom = element.firstChild.style.bottom;
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'box',
|
||||
scaleFrom: 100,
|
||||
restoreAfterFinish: true,
|
||||
beforeStartInternal: function(effect) {
|
||||
Element.makePositioned(effect.element.firstChild);
|
||||
if (window.opera) effect.element.firstChild.style.top = "";
|
||||
Element.makeClipping(effect.element);
|
||||
Element.show(element);
|
||||
},
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.style.bottom =
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px'; },
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
Element.undoPositioned(effect.element.firstChild);
|
||||
effect.element.firstChild.style.bottom = oldInnerBottom; }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.Squish = function(element) {
|
||||
// Bug in opera makes the TD containing this element expand for a instance after finish
|
||||
return new Effect.Scale(element, window.opera ? 1 : 0,
|
||||
{ restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
Element.makeClipping(effect.element); },
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element); }
|
||||
});
|
||||
}
|
||||
|
||||
Effect.Grow = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
|
||||
var elementDimensions = Element.getDimensions(element);
|
||||
var originalWidth = elementDimensions.width;
|
||||
var originalHeight = elementDimensions.height;
|
||||
var oldTop = element.style.top;
|
||||
var oldLeft = element.style.left;
|
||||
var oldHeight = element.style.height;
|
||||
var oldWidth = element.style.width;
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
|
||||
var direction = options.direction || 'center';
|
||||
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
|
||||
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
|
||||
var opacityTransition = options.opacityTransition || Effect.Transitions.full;
|
||||
|
||||
var initialMoveX, initialMoveY;
|
||||
var moveX, moveY;
|
||||
|
||||
switch (direction) {
|
||||
case 'top-left':
|
||||
initialMoveX = initialMoveY = moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
initialMoveX = originalWidth;
|
||||
initialMoveY = moveY = 0;
|
||||
moveX = -originalWidth;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
initialMoveX = moveX = 0;
|
||||
initialMoveY = originalHeight;
|
||||
moveY = -originalHeight;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
initialMoveX = originalWidth;
|
||||
initialMoveY = originalHeight;
|
||||
moveX = -originalWidth;
|
||||
moveY = -originalHeight;
|
||||
break;
|
||||
case 'center':
|
||||
initialMoveX = originalWidth / 2;
|
||||
initialMoveY = originalHeight / 2;
|
||||
moveX = -originalWidth / 2;
|
||||
moveY = -originalHeight / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
|
||||
duration: 0.01,
|
||||
beforeSetup: function(effect) {
|
||||
Element.hide(effect.element);
|
||||
Element.makeClipping(effect.element);
|
||||
Element.makePositioned(effect.element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Parallel(
|
||||
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
|
||||
new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
|
||||
new Effect.Scale(effect.element, 100, {
|
||||
scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
|
||||
sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
|
||||
], Object.extend({
|
||||
beforeSetup: function(effect) {
|
||||
effect.effects[0].element.style.height = 0;
|
||||
Element.show(effect.effects[0].element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
var el = effect.effects[0].element;
|
||||
var els = el.style;
|
||||
Element.undoClipping(el);
|
||||
Element.undoPositioned(el);
|
||||
els.top = oldTop;
|
||||
els.left = oldLeft;
|
||||
els.height = oldHeight;
|
||||
els.width = originalWidth + 'px';
|
||||
Element.setInlineOpacity(el, oldOpacity);
|
||||
}
|
||||
}, options)
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Effect.Shrink = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
|
||||
var originalWidth = element.clientWidth;
|
||||
var originalHeight = element.clientHeight;
|
||||
var oldTop = element.style.top;
|
||||
var oldLeft = element.style.left;
|
||||
var oldHeight = element.style.height;
|
||||
var oldWidth = element.style.width;
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
|
||||
var direction = options.direction || 'center';
|
||||
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
|
||||
var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal;
|
||||
var opacityTransition = options.opacityTransition || Effect.Transitions.none;
|
||||
|
||||
var moveX, moveY;
|
||||
|
||||
switch (direction) {
|
||||
case 'top-left':
|
||||
moveX = moveY = 0;
|
||||
break;
|
||||
case 'top-right':
|
||||
moveX = originalWidth;
|
||||
moveY = 0;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
moveX = 0;
|
||||
moveY = originalHeight;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
moveX = originalWidth;
|
||||
moveY = originalHeight;
|
||||
break;
|
||||
case 'center':
|
||||
moveX = originalWidth / 2;
|
||||
moveY = originalHeight / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
|
||||
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
|
||||
new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
|
||||
], Object.extend({
|
||||
beforeStartInternal: function(effect) {
|
||||
Element.makePositioned(effect.effects[0].element);
|
||||
Element.makeClipping(effect.effects[0].element);
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
var el = effect.effects[0].element;
|
||||
var els = el.style;
|
||||
Element.hide(el);
|
||||
Element.undoClipping(el);
|
||||
Element.undoPositioned(el);
|
||||
els.top = oldTop;
|
||||
els.left = oldLeft;
|
||||
els.height = oldHeight;
|
||||
els.width = oldWidth;
|
||||
Element.setInlineOpacity(el, oldOpacity);
|
||||
}
|
||||
}, options)
|
||||
);
|
||||
}
|
||||
|
||||
Effect.Pulsate = function(element) {
|
||||
element = $(element);
|
||||
var options = arguments[1] || {};
|
||||
var oldOpacity = Element.getInlineOpacity(element);
|
||||
var transition = options.transition || Effect.Transitions.sinoidal;
|
||||
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 3.0, from: 0,
|
||||
afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
|
||||
}, options), {transition: reverser}));
|
||||
}
|
||||
|
||||
Effect.Fold = function(element) {
|
||||
element = $(element);
|
||||
var originalTop = element.style.top;
|
||||
var originalLeft = element.style.left;
|
||||
var originalWidth = element.style.width;
|
||||
var originalHeight = element.style.height;
|
||||
Element.makeClipping(element);
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
scaleY: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
Element.hide(effect.element);
|
||||
Element.undoClipping(effect.element);
|
||||
effect.element.style.top = originalTop;
|
||||
effect.element.style.left = originalLeft;
|
||||
effect.element.style.width = originalWidth;
|
||||
effect.element.style.height = originalHeight;
|
||||
} });
|
||||
}}, arguments[1] || {}));
|
||||
}
|
48
thirdparty/scriptaculous/scriptaculous.js
vendored
48
thirdparty/scriptaculous/scriptaculous.js
vendored
@ -1,48 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var Scriptaculous = {
|
||||
Version: '1.5_rc5',
|
||||
require: function(libraryName) {
|
||||
// inserting via DOM fails in Safari 2.0, so brute force approach
|
||||
document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
|
||||
},
|
||||
load: function() {
|
||||
if((typeof Prototype=='undefined') ||
|
||||
parseFloat(Prototype.Version.split(".")[0] + "." +
|
||||
Prototype.Version.split(".")[1]) < 1.4)
|
||||
throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
|
||||
var scriptTags = document.getElementsByTagName("script");
|
||||
for(var i=0;i<scriptTags.length;i++) {
|
||||
if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js(\?.*)?$/)) {
|
||||
var path = scriptTags[i].src.replace(/scriptaculous\.js(\?.*)?$/,'');
|
||||
this.require(path + 'builder.js');
|
||||
this.require(path + 'effects.js');
|
||||
this.require(path + 'dragdrop.js');
|
||||
this.require(path + 'controls.js');
|
||||
this.require(path + 'slider.js');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Scriptaculous.load();
|
275
thirdparty/scriptaculous/slider.js
vendored
275
thirdparty/scriptaculous/slider.js
vendored
@ -1,275 +0,0 @@
|
||||
// Copyright (c) 2005 Marty Haught, Thomas Fuchs
|
||||
//
|
||||
// See http://script.aculo.us for more info
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
if(!Control) var Control = {};
|
||||
Control.Slider = Class.create();
|
||||
|
||||
// options:
|
||||
// axis: 'vertical', or 'horizontal' (default)
|
||||
//
|
||||
// callbacks:
|
||||
// onChange(value)
|
||||
// onSlide(value)
|
||||
Control.Slider.prototype = {
|
||||
initialize: function(handle, track, options) {
|
||||
var slider = this;
|
||||
|
||||
if(handle instanceof Array) {
|
||||
this.handles = handle.collect( function(e) { return $(e) });
|
||||
} else {
|
||||
this.handles = [$(handle)];
|
||||
}
|
||||
|
||||
this.track = $(track);
|
||||
this.options = options || {};
|
||||
|
||||
this.axis = this.options.axis || 'horizontal';
|
||||
this.increment = this.options.increment || 1;
|
||||
this.step = parseInt(this.options.step || '1');
|
||||
this.range = this.options.range || $R(0,1);
|
||||
|
||||
this.value = 0; // assure backwards compat
|
||||
this.values = this.handles.map( function() { return 0 });
|
||||
this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
|
||||
this.options.startSpan = $(this.options.startSpan || null);
|
||||
this.options.endSpan = $(this.options.endSpan || null);
|
||||
|
||||
this.restricted = this.options.restricted || false;
|
||||
|
||||
this.maximum = this.options.maximum || this.range.end;
|
||||
this.minimum = this.options.minimum || this.range.start;
|
||||
|
||||
// Will be used to align the handle onto the track, if necessary
|
||||
this.alignX = parseInt(this.options.alignX || '0');
|
||||
this.alignY = parseInt(this.options.alignY || '0');
|
||||
|
||||
this.trackLength = this.maximumOffset() - this.minimumOffset();
|
||||
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
this.disabled = false;
|
||||
|
||||
if(this.options.disabled) this.setDisabled();
|
||||
|
||||
// Allowed values array
|
||||
this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
|
||||
if(this.allowedValues) {
|
||||
this.minimum = this.allowedValues.min();
|
||||
this.maximum = this.allowedValues.max();
|
||||
}
|
||||
|
||||
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.update.bindAsEventListener(this);
|
||||
|
||||
// Initialize handles in reverse (make sure first handle is active)
|
||||
this.handles.each( function(h,i) {
|
||||
i = slider.handles.length-1-i;
|
||||
slider.setValue(parseFloat(
|
||||
(slider.options.sliderValue instanceof Array ?
|
||||
slider.options.sliderValue[i] : slider.options.sliderValue) ||
|
||||
slider.range.start), i);
|
||||
Element.makePositioned(h); // fix IE
|
||||
Event.observe(h, "mousedown", slider.eventMouseDown);
|
||||
});
|
||||
|
||||
Event.observe(this.track, "mousedown", this.eventMouseDown);
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
|
||||
this.initialized = true;
|
||||
},
|
||||
dispose: function() {
|
||||
var slider = this;
|
||||
Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
this.handles.each( function(h) {
|
||||
Event.stopObserving(h, "mousedown", slider.eventMouseDown);
|
||||
});
|
||||
},
|
||||
setDisabled: function(){
|
||||
this.disabled = true;
|
||||
},
|
||||
setEnabled: function(){
|
||||
this.disabled = false;
|
||||
},
|
||||
getNearestValue: function(value){
|
||||
if(this.allowedValues){
|
||||
if(value >= this.allowedValues.max()) return(this.allowedValues.max());
|
||||
if(value <= this.allowedValues.min()) return(this.allowedValues.min());
|
||||
|
||||
var offset = Math.abs(this.allowedValues[0] - value);
|
||||
var newValue = this.allowedValues[0];
|
||||
this.allowedValues.each( function(v) {
|
||||
var currentOffset = Math.abs(v - value);
|
||||
if(currentOffset <= offset){
|
||||
newValue = v;
|
||||
offset = currentOffset;
|
||||
}
|
||||
});
|
||||
return newValue;
|
||||
}
|
||||
if(value > this.range.end) return this.range.end;
|
||||
if(value < this.range.start) return this.range.start;
|
||||
return value;
|
||||
},
|
||||
setValue: function(sliderValue, handleIdx){
|
||||
if(!this.active) {
|
||||
this.activeHandle = this.handles[handleIdx];
|
||||
this.activeHandleIdx = handleIdx;
|
||||
this.updateStyles();
|
||||
}
|
||||
handleIdx = handleIdx || this.activeHandleIdx || 0;
|
||||
if(this.initialized && this.restricted) {
|
||||
if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
|
||||
sliderValue = this.values[handleIdx-1];
|
||||
if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
|
||||
sliderValue = this.values[handleIdx+1];
|
||||
}
|
||||
sliderValue = this.getNearestValue(sliderValue);
|
||||
this.values[handleIdx] = sliderValue;
|
||||
this.value = this.values[0]; // assure backwards compat
|
||||
|
||||
this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
|
||||
this.translateToPx(sliderValue);
|
||||
|
||||
this.drawSpans();
|
||||
if(!this.dragging || !this.event) this.updateFinished();
|
||||
},
|
||||
setValueBy: function(delta, handleIdx) {
|
||||
this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
|
||||
handleIdx || this.activeHandleIdx || 0);
|
||||
},
|
||||
translateToPx: function(value) {
|
||||
return Math.round((this.trackLength / (this.range.end - this.range.start)) * (value - this.range.start)) + "px";
|
||||
},
|
||||
translateToValue: function(offset) {
|
||||
return ((offset/this.trackLength) * (this.range.end - this.range.start)) + this.range.start;
|
||||
},
|
||||
getRange: function(range) {
|
||||
var v = this.values.sortBy(Prototype.K);
|
||||
range = range || 0;
|
||||
return $R(v[range],v[range+1]);
|
||||
},
|
||||
minimumOffset: function(){
|
||||
return(this.isVertical() ? this.alignY : this.alignX);
|
||||
},
|
||||
maximumOffset: function(){
|
||||
return(this.isVertical() ?
|
||||
this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
|
||||
},
|
||||
isVertical: function(){
|
||||
return (this.axis == 'vertical');
|
||||
},
|
||||
drawSpans: function() {
|
||||
var slider = this;
|
||||
if(this.spans)
|
||||
$R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
|
||||
if(this.options.startSpan)
|
||||
this.setSpan(this.options.startSpan,
|
||||
$R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
|
||||
if(this.options.endSpan)
|
||||
this.setSpan(this.options.endSpan,
|
||||
$R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
|
||||
},
|
||||
setSpan: function(span, range) {
|
||||
if(this.isVertical()) {
|
||||
span.style.top = this.translateToPx(range.start);
|
||||
span.style.height = this.translateToPx(range.end - range.start);
|
||||
} else {
|
||||
span.style.left = this.translateToPx(range.start);
|
||||
span.style.width = this.translateToPx(range.end - range.start);
|
||||
}
|
||||
},
|
||||
updateStyles: function() {
|
||||
this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
|
||||
Element.addClassName(this.activeHandle, 'selected');
|
||||
},
|
||||
startDrag: function(event) {
|
||||
if(Event.isLeftClick(event)) {
|
||||
if(!this.disabled){
|
||||
this.active = true;
|
||||
|
||||
var handle = Event.element(event);
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
if(handle==this.track) {
|
||||
var offsets = Position.cumulativeOffset(this.track);
|
||||
this.event = event;
|
||||
this.setValue(this.translateToValue(
|
||||
this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0]
|
||||
));
|
||||
} else {
|
||||
// find the handle (prevents issues with Safari)
|
||||
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
|
||||
handle = handle.parentNode;
|
||||
|
||||
this.activeHandle = handle;
|
||||
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
|
||||
this.updateStyles();
|
||||
|
||||
var offsets = Position.cumulativeOffset(this.activeHandle);
|
||||
this.offsetX = (pointer[0] - offsets[0]);
|
||||
this.offsetY = (pointer[1] - offsets[1]);
|
||||
}
|
||||
}
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
update: function(event) {
|
||||
if(this.active) {
|
||||
if(!this.dragging) this.dragging = true;
|
||||
this.draw(event);
|
||||
// fix AppleWebKit rendering
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
draw: function(event) {
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var offsets = Position.cumulativeOffset(this.track);
|
||||
pointer[0] -= this.offsetX + offsets[0];
|
||||
pointer[1] -= this.offsetY + offsets[1];
|
||||
this.event = event;
|
||||
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
|
||||
if(this.initialized && this.options.onSlide) this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
|
||||
},
|
||||
endDrag: function(event) {
|
||||
if(this.active && this.dragging) {
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
}
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
},
|
||||
finishDrag: function(event, success) {
|
||||
this.active = false;
|
||||
this.dragging = false;
|
||||
this.updateFinished();
|
||||
},
|
||||
updateFinished: function() {
|
||||
if(this.initialized && this.options.onChange)
|
||||
this.options.onChange(this.values.length>1 ? this.values : this.value, this);
|
||||
this.event = null;
|
||||
}
|
||||
}
|
363
thirdparty/scriptaculous/unittest.js
vendored
363
thirdparty/scriptaculous/unittest.js
vendored
@ -1,363 +0,0 @@
|
||||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// experimental, Firefox-only
|
||||
Event.simulateMouse = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
buttons: 0
|
||||
}, arguments[2] || {});
|
||||
var oEvent = document.createEvent("MouseEvents");
|
||||
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
|
||||
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
|
||||
false, false, false, false, 0, $(element));
|
||||
|
||||
if(this.mark) Element.remove(this.mark);
|
||||
this.mark = document.createElement('div');
|
||||
this.mark.appendChild(document.createTextNode(" "));
|
||||
document.body.appendChild(this.mark);
|
||||
this.mark.style.position = 'absolute';
|
||||
this.mark.style.top = options.pointerY + "px";
|
||||
this.mark.style.left = options.pointerX + "px";
|
||||
this.mark.style.width = "5px";
|
||||
this.mark.style.height = "5px;";
|
||||
this.mark.style.borderTop = "1px solid red;"
|
||||
this.mark.style.borderLeft = "1px solid red;"
|
||||
|
||||
if(this.step)
|
||||
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
|
||||
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
|
||||
// You need to downgrade to 1.0.4 for now to get this working
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
|
||||
Event.simulateKey = function(element, eventName) {
|
||||
var options = Object.extend({
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
keyCode: 0,
|
||||
charCode: 0
|
||||
}, arguments[2] || {});
|
||||
|
||||
var oEvent = document.createEvent("KeyEvents");
|
||||
oEvent.initKeyEvent(eventName, true, true, window,
|
||||
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
|
||||
options.keyCode, options.charCode );
|
||||
$(element).dispatchEvent(oEvent);
|
||||
};
|
||||
|
||||
Event.simulateKeys = function(element, command) {
|
||||
for(var i=0; i<command.length; i++) {
|
||||
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
|
||||
}
|
||||
};
|
||||
|
||||
var Test = {}
|
||||
Test.Unit = {};
|
||||
|
||||
// security exception workaround
|
||||
Test.Unit.inspect = function(obj) {
|
||||
var info = [];
|
||||
|
||||
if(typeof obj=="string" ||
|
||||
typeof obj=="number") {
|
||||
return obj;
|
||||
} else {
|
||||
for(property in obj)
|
||||
if(typeof obj[property]!="function")
|
||||
info.push(property + ' => ' +
|
||||
(typeof obj[property] == "string" ?
|
||||
'"' + obj[property] + '"' :
|
||||
obj[property]));
|
||||
}
|
||||
|
||||
return ("'" + obj + "' #" + typeof obj +
|
||||
": {" + info.join(", ") + "}");
|
||||
}
|
||||
|
||||
Test.Unit.Logger = Class.create();
|
||||
Test.Unit.Logger.prototype = {
|
||||
initialize: function(log) {
|
||||
this.log = $(log);
|
||||
if (this.log) {
|
||||
this._createLogTable();
|
||||
}
|
||||
},
|
||||
start: function(testName) {
|
||||
if (!this.log) return;
|
||||
this.testName = testName;
|
||||
this.lastLogLine = document.createElement('tr');
|
||||
this.statusCell = document.createElement('td');
|
||||
this.nameCell = document.createElement('td');
|
||||
this.nameCell.appendChild(document.createTextNode(testName));
|
||||
this.messageCell = document.createElement('td');
|
||||
this.lastLogLine.appendChild(this.statusCell);
|
||||
this.lastLogLine.appendChild(this.nameCell);
|
||||
this.lastLogLine.appendChild(this.messageCell);
|
||||
this.loglines.appendChild(this.lastLogLine);
|
||||
},
|
||||
finish: function(status, summary) {
|
||||
if (!this.log) return;
|
||||
this.lastLogLine.className = status;
|
||||
this.statusCell.innerHTML = status;
|
||||
this.messageCell.innerHTML = this._toHTML(summary);
|
||||
},
|
||||
message: function(message) {
|
||||
if (!this.log) return;
|
||||
this.messageCell.innerHTML = this._toHTML(message);
|
||||
},
|
||||
summary: function(summary) {
|
||||
if (!this.log) return;
|
||||
this.logsummary.innerHTML = this._toHTML(summary);
|
||||
},
|
||||
_createLogTable: function() {
|
||||
this.log.innerHTML =
|
||||
'<div id="logsummary"></div>' +
|
||||
'<table id="logtable">' +
|
||||
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
|
||||
'<tbody id="loglines"></tbody>' +
|
||||
'</table>';
|
||||
this.logsummary = $('logsummary')
|
||||
this.loglines = $('loglines');
|
||||
},
|
||||
_toHTML: function(txt) {
|
||||
return txt.escapeHTML().replace(/\n/g,"<br/>");
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Runner = Class.create();
|
||||
Test.Unit.Runner.prototype = {
|
||||
initialize: function(testcases) {
|
||||
this.options = Object.extend({
|
||||
testLog: 'testlog'
|
||||
}, arguments[1] || {});
|
||||
this.options.resultsURL = this.parseResultsURLQueryParameter();
|
||||
if (this.options.testLog) {
|
||||
this.options.testLog = $(this.options.testLog) || null;
|
||||
}
|
||||
if(this.options.tests) {
|
||||
this.tests = [];
|
||||
for(var i = 0; i < this.options.tests.length; i++) {
|
||||
if(/^test/.test(this.options.tests[i])) {
|
||||
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.options.test) {
|
||||
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
|
||||
} else {
|
||||
this.tests = [];
|
||||
for(var testcase in testcases) {
|
||||
if(/^test/.test(testcase)) {
|
||||
this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.currentTest = 0;
|
||||
this.logger = new Test.Unit.Logger(this.options.testLog);
|
||||
setTimeout(this.runTests.bind(this), 1000);
|
||||
},
|
||||
parseResultsURLQueryParameter: function() {
|
||||
return window.location.search.parseQuery()["resultsURL"];
|
||||
},
|
||||
// Returns:
|
||||
// "ERROR" if there was an error,
|
||||
// "FAILURE" if there was a failure, or
|
||||
// "SUCCESS" if there was neither
|
||||
getResult: function() {
|
||||
var hasFailure = false;
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
if (this.tests[i].errors > 0) {
|
||||
return "ERROR";
|
||||
}
|
||||
if (this.tests[i].failures > 0) {
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
if (hasFailure) {
|
||||
return "FAILURE";
|
||||
} else {
|
||||
return "SUCCESS";
|
||||
}
|
||||
},
|
||||
postResults: function() {
|
||||
if (this.options.resultsURL) {
|
||||
new Ajax.Request(this.options.resultsURL,
|
||||
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
|
||||
}
|
||||
},
|
||||
runTests: function() {
|
||||
var test = this.tests[this.currentTest];
|
||||
if (!test) {
|
||||
// finished!
|
||||
this.postResults();
|
||||
this.logger.summary(this.summary());
|
||||
return;
|
||||
}
|
||||
if(!test.isWaiting) {
|
||||
this.logger.start(test.name);
|
||||
}
|
||||
test.run();
|
||||
if(test.isWaiting) {
|
||||
this.logger.message("Waiting for " + test.timeToWait + "ms");
|
||||
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
|
||||
} else {
|
||||
this.logger.finish(test.status(), test.summary());
|
||||
this.currentTest++;
|
||||
// tail recursive, hopefully the browser will skip the stackframe
|
||||
this.runTests();
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var assertions = 0;
|
||||
var failures = 0;
|
||||
var errors = 0;
|
||||
var messages = [];
|
||||
for(var i=0;i<this.tests.length;i++) {
|
||||
assertions += this.tests[i].assertions;
|
||||
failures += this.tests[i].failures;
|
||||
errors += this.tests[i].errors;
|
||||
}
|
||||
return (
|
||||
this.tests.length + " tests, " +
|
||||
assertions + " assertions, " +
|
||||
failures + " failures, " +
|
||||
errors + " errors");
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Assertions = Class.create();
|
||||
Test.Unit.Assertions.prototype = {
|
||||
initialize: function() {
|
||||
this.assertions = 0;
|
||||
this.failures = 0;
|
||||
this.errors = 0;
|
||||
this.messages = [];
|
||||
},
|
||||
summary: function() {
|
||||
return (
|
||||
this.assertions + " assertions, " +
|
||||
this.failures + " failures, " +
|
||||
this.errors + " errors" + "\n" +
|
||||
this.messages.join("\n"));
|
||||
},
|
||||
pass: function() {
|
||||
this.assertions++;
|
||||
},
|
||||
fail: function(message) {
|
||||
this.failures++;
|
||||
this.messages.push("Failure: " + message);
|
||||
},
|
||||
error: function(error) {
|
||||
this.errors++;
|
||||
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
|
||||
},
|
||||
status: function() {
|
||||
if (this.failures > 0) return 'failed';
|
||||
if (this.errors > 0) return 'error';
|
||||
return 'passed';
|
||||
},
|
||||
assert: function(expression) {
|
||||
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
|
||||
try { expression ? this.pass() :
|
||||
this.fail(message); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertEqual";
|
||||
try { (expected == actual) ? this.pass() :
|
||||
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
|
||||
'", actual "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotEqual: function(expected, actual) {
|
||||
var message = arguments[2] || "assertNotEqual";
|
||||
try { (expected != actual) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNull: function(obj) {
|
||||
var message = arguments[1] || 'assertNull'
|
||||
try { (obj==null) ? this.pass() :
|
||||
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertHidden: function(element) {
|
||||
var message = arguments[1] || 'assertHidden';
|
||||
this.assertEqual("none", element.style.display, message);
|
||||
},
|
||||
assertNotNull: function(object) {
|
||||
var message = arguments[1] || 'assertNotNull';
|
||||
this.assert(object != null, message);
|
||||
},
|
||||
assertInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertInstanceOf';
|
||||
try {
|
||||
(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was not an instance of the expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
assertNotInstanceOf: function(expected, actual) {
|
||||
var message = arguments[2] || 'assertNotInstanceOf';
|
||||
try {
|
||||
!(actual instanceof expected) ? this.pass() :
|
||||
this.fail(message + ": object was an instance of the not expected type"); }
|
||||
catch(e) { this.error(e); }
|
||||
},
|
||||
_isVisible: function(element) {
|
||||
element = $(element);
|
||||
if(!element.parentNode) return true;
|
||||
this.assertNotNull(element);
|
||||
if(element.style && Element.getStyle(element, 'display') == 'none')
|
||||
return false;
|
||||
|
||||
return this._isVisible(element.parentNode);
|
||||
},
|
||||
assertNotVisible: function(element) {
|
||||
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
|
||||
},
|
||||
assertVisible: function(element) {
|
||||
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
|
||||
}
|
||||
}
|
||||
|
||||
Test.Unit.Testcase = Class.create();
|
||||
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
|
||||
initialize: function(name, test, setup, teardown) {
|
||||
Test.Unit.Assertions.prototype.initialize.bind(this)();
|
||||
this.name = name;
|
||||
this.test = test || function() {};
|
||||
this.setup = setup || function() {};
|
||||
this.teardown = teardown || function() {};
|
||||
this.isWaiting = false;
|
||||
this.timeToWait = 1000;
|
||||
},
|
||||
wait: function(time, nextPart) {
|
||||
this.isWaiting = true;
|
||||
this.test = nextPart;
|
||||
this.timeToWait = time;
|
||||
},
|
||||
run: function() {
|
||||
try {
|
||||
try {
|
||||
if (!this.isWaiting) this.setup.bind(this)();
|
||||
this.isWaiting = false;
|
||||
this.test.bind(this)();
|
||||
} finally {
|
||||
if(!this.isWaiting) {
|
||||
this.teardown.bind(this)();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) { this.error(e); }
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user