mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Cleanup and refactor of select fields
API Standardise Relation interface
This commit is contained in:
parent
d8ea6ceccc
commit
bdb1a95758
10
admin/thirdparty/chosen/chosen/chosen.jquery.js
vendored
10
admin/thirdparty/chosen/chosen/chosen.jquery.js
vendored
@ -49,7 +49,8 @@
|
||||
|
||||
SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
|
||||
if (option.nodeName.toUpperCase() === "OPTION") {
|
||||
if (option.text !== "") {
|
||||
// workaround for https://github.com/harvesthq/chosen/issues/2125
|
||||
if (!option.text.match(/^\s*$/g)) {
|
||||
if (group_position != null) {
|
||||
this.parsed[group_position].children += 1;
|
||||
}
|
||||
@ -135,7 +136,12 @@ Copyright (c) 2011 by Harvest
|
||||
this.results_showing = false;
|
||||
this.result_highlighted = null;
|
||||
this.result_single_selected = null;
|
||||
this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
|
||||
this.allow_single_deselect = (this.options.allow_single_deselect != null)
|
||||
&& (this.form_field.options[0] != null)
|
||||
// workaround for https://github.com/harvesthq/chosen/issues/2125
|
||||
&& this.form_field.options[0].text.match(/^\s*$/g)
|
||||
? this.options.allow_single_deselect
|
||||
: false;
|
||||
this.disable_search_threshold = this.options.disable_search_threshold || 0;
|
||||
this.disable_search = this.options.disable_search || false;
|
||||
this.search_contains = this.options.search_contains || false;
|
||||
|
@ -498,3 +498,18 @@ configuration customisation is done via overriding these templates.
|
||||
If upgrading from an existing installation, make sure to invoke ?flush=all at least once.
|
||||
|
||||
See [/developer_guides/files/file_security] for more information.
|
||||
|
||||
### `ListboxField` is now multiple-only
|
||||
|
||||
Previously, this field would operate as either a single select (default) or multi-select by setting
|
||||
`setMultiple` to either true or false.
|
||||
|
||||
Now this field should only be used for multi-selection. Single-selection should be done using
|
||||
a regular `DropdownField`.
|
||||
|
||||
### `GroupedDropdownField::setDisabled` now only accepts a list of values.
|
||||
|
||||
Where previously you could specify a list of grouped values in the same way as `setSource`, this
|
||||
method now only accepts either a non-associative array of values (not titles) or an `SS_List`
|
||||
of items to disable.
|
||||
|
||||
|
@ -36,12 +36,7 @@
|
||||
* @package forms
|
||||
* @subpackage fields-basic
|
||||
*/
|
||||
class CheckboxSetField extends OptionsetField {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultItems = array();
|
||||
class CheckboxSetField extends MultiSelectField {
|
||||
|
||||
/**
|
||||
* @todo Explain different source data that can be used with this field,
|
||||
@ -60,306 +55,43 @@ class CheckboxSetField extends OptionsetField {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of options to render in this formfield
|
||||
*
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function getOptions() {
|
||||
$selectedValues = $this->getValueArray();
|
||||
$defaultItems = $this->getDefaultItems();
|
||||
|
||||
// Generate list of options to display
|
||||
$odd = 0;
|
||||
|
||||
$source = $this->source;
|
||||
$values = $this->value;
|
||||
$items = array();
|
||||
|
||||
// Get values from the join, if available
|
||||
if(is_object($this->form)) {
|
||||
$record = $this->form->getRecord();
|
||||
if(!$values && $record && $record->hasMethod($this->name)) {
|
||||
$funcName = $this->name;
|
||||
$join = $record->$funcName();
|
||||
if($join) {
|
||||
foreach($join as $joinItem) {
|
||||
$values[] = $joinItem->ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Source is not an array
|
||||
if(!is_array($source) && !is_a($source, 'SQLMap')) {
|
||||
if(is_array($values)) {
|
||||
$items = $values;
|
||||
} else {
|
||||
// Source and values are DataObject sets.
|
||||
if($values && is_a($values, 'SS_List')) {
|
||||
foreach($values as $object) {
|
||||
if(is_a($object, 'DataObject')) {
|
||||
$items[] = $object->ID;
|
||||
}
|
||||
}
|
||||
} elseif($values && is_string($values)) {
|
||||
if(!empty($values)) {
|
||||
$items = explode(',', $values);
|
||||
$items = str_replace('{comma}', ',', $items);
|
||||
} else {
|
||||
$items = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Sometimes we pass a singluar default value thats ! an array && !SS_List
|
||||
if($values instanceof SS_List || is_array($values)) {
|
||||
$items = $values;
|
||||
} else {
|
||||
if($values === null) {
|
||||
$items = array();
|
||||
}
|
||||
else {
|
||||
if(!empty($values)) {
|
||||
$items = explode(',', $values);
|
||||
$items = str_replace('{comma}', ',', $items);
|
||||
} else {
|
||||
$items = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(is_array($source)) {
|
||||
unset($source['']);
|
||||
}
|
||||
|
||||
$options = array();
|
||||
|
||||
if ($source == null) {
|
||||
$source = array();
|
||||
}
|
||||
|
||||
foreach($source as $value => $item) {
|
||||
if($item instanceof DataObject) {
|
||||
$value = $item->ID;
|
||||
$title = $item->Title;
|
||||
} else {
|
||||
$title = $item;
|
||||
}
|
||||
|
||||
$itemID = $this->ID() . '_' . preg_replace('/[^a-zA-Z0-9]/', '', $value);
|
||||
$formID = $this->ID();
|
||||
$options = new ArrayList();
|
||||
foreach($this->getSource() as $itemValue => $title) {
|
||||
$itemID = Convert::raw2htmlid("{$formID}_{$itemValue}");
|
||||
$odd = ($odd + 1) % 2;
|
||||
$extraClass = $odd ? 'odd' : 'even';
|
||||
$extraClass .= ' val' . preg_replace('/[^a-zA-Z0-9\-\_]/', '_', $value);
|
||||
$extraClass .= ' val' . preg_replace('/[^a-zA-Z0-9\-\_]/', '_', $itemValue);
|
||||
|
||||
$itemChecked = in_array($itemValue, $selectedValues) || in_array($itemValue, $defaultItems);
|
||||
$itemDisabled = $this->isDisabled() || in_array($itemValue, $defaultItems);
|
||||
|
||||
$options[] = new ArrayData(array(
|
||||
$options->push(new ArrayData(array(
|
||||
'ID' => $itemID,
|
||||
'Class' => $extraClass,
|
||||
'Name' => "{$this->name}[{$value}]",
|
||||
'Value' => $value,
|
||||
'Name' => "{$this->name}[{$itemValue}]",
|
||||
'Value' => $itemValue,
|
||||
'Title' => $title,
|
||||
'isChecked' => in_array($value, $items) || in_array($value, $this->defaultItems),
|
||||
'isDisabled' => $this->disabled || in_array($value, $this->disabledItems)
|
||||
));
|
||||
'isChecked' => $itemChecked,
|
||||
'isDisabled' => $itemDisabled,
|
||||
)));
|
||||
}
|
||||
|
||||
$options = new ArrayList($options);
|
||||
|
||||
$this->extend('updateGetOptions', $options);
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default selections, regardless of the {@link setValue()} settings.
|
||||
* Note: Items marked as disabled through {@link setDisabledItems()} can still be
|
||||
* selected by default through this method.
|
||||
*
|
||||
* @param Array $items Collection of array keys, as defined in the $source array
|
||||
*/
|
||||
public function setDefaultItems($items) {
|
||||
$this->defaultItems = $items;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array
|
||||
*/
|
||||
public function getDefaultItems() {
|
||||
return $this->defaultItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value into this CheckboxSetField
|
||||
*/
|
||||
public function setValue($value, $obj = null) {
|
||||
// If we're not passed a value directly, we can look for it in a relation method on the object passed as a
|
||||
// second arg
|
||||
if(!$value && $obj && $obj instanceof DataObject && $obj->hasMethod($this->name)) {
|
||||
$funcName = $this->name;
|
||||
$value = $obj->$funcName()->getIDList();
|
||||
}
|
||||
|
||||
parent::setValue($value, $obj);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current value of this CheckboxSetField into a DataObject.
|
||||
* If the field it is saving to is a has_many or many_many relationship,
|
||||
* it is saved by setByIDList(), otherwise it creates a comma separated
|
||||
* list for a standard DB text/varchar field.
|
||||
*
|
||||
* @param DataObject $record The record to save into
|
||||
*/
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
$fieldname = $this->name;
|
||||
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
||||
if($fieldname && $record && $relation &&
|
||||
($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||
$idList = array();
|
||||
if($this->value) foreach($this->value as $id => $bool) {
|
||||
if($bool) {
|
||||
$idList[] = $id;
|
||||
}
|
||||
}
|
||||
$relation->setByIDList($idList);
|
||||
} elseif($fieldname && $record) {
|
||||
if($this->value) {
|
||||
$this->value = str_replace(',', '{comma}', $this->value);
|
||||
$record->$fieldname = implode(',', (array) $this->value);
|
||||
} else {
|
||||
$record->$fieldname = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CheckboxSetField value as a string
|
||||
* selected item keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dataValue() {
|
||||
if($this->value && is_array($this->value)) {
|
||||
$filtered = array();
|
||||
foreach($this->value as $item) {
|
||||
if($item) {
|
||||
$filtered[] = str_replace(",", "{comma}", $item);
|
||||
}
|
||||
}
|
||||
|
||||
return implode(',', $filtered);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function performDisabledTransformation() {
|
||||
$clone = clone $this;
|
||||
$clone->setDisabled(true);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the source data for this CheckboxSetField
|
||||
* into a comma separated list of values.
|
||||
*
|
||||
* @return ReadonlyField
|
||||
*/
|
||||
public function performReadonlyTransformation() {
|
||||
$values = '';
|
||||
$data = array();
|
||||
|
||||
$items = $this->value;
|
||||
if($this->source) {
|
||||
foreach($this->source as $source) {
|
||||
if(is_object($source)) {
|
||||
$sourceTitles[$source->ID] = $source->Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($items) {
|
||||
// Items is a DO Set
|
||||
if($items instanceof SS_List) {
|
||||
foreach($items as $item) {
|
||||
$data[] = $item->Title;
|
||||
}
|
||||
if($data) $values = implode(', ', $data);
|
||||
|
||||
// Items is an array or single piece of string (including comma seperated string)
|
||||
} else {
|
||||
if(!is_array($items)) {
|
||||
$items = preg_split('/ *, */', trim($items));
|
||||
}
|
||||
|
||||
foreach($items as $item) {
|
||||
if(is_array($item)) {
|
||||
$data[] = $item['Title'];
|
||||
} elseif(is_array($this->source) && !empty($this->source[$item])) {
|
||||
$data[] = $this->source[$item];
|
||||
} elseif(is_a($this->source, 'SS_List')) {
|
||||
$data[] = $sourceTitles[$item];
|
||||
} else {
|
||||
$data[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$values = implode(', ', $data);
|
||||
}
|
||||
}
|
||||
|
||||
$field = $this->castedCopy('ReadonlyField');
|
||||
$field->setValue($values);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function Type() {
|
||||
return 'optionset checkboxset';
|
||||
}
|
||||
|
||||
public function ExtraOptions() {
|
||||
return FormField::ExtraOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$values = $this->value;
|
||||
if (!$values) {
|
||||
return true;
|
||||
}
|
||||
$sourceArray = $this->getSourceAsArray();
|
||||
if (is_array($values)) {
|
||||
if (!array_intersect_key($sourceArray, $values)) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'CheckboxSetField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. '{value}' is not a valid option",
|
||||
array('value' => implode(' and ', array_diff($sourceArray, $values)))
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!in_array($this->value, $sourceArray)) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'CheckboxSetField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. '{value}' is not a valid option",
|
||||
array('value' => $this->value)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,13 +11,18 @@ class CountryDropdownField extends DropdownField {
|
||||
|
||||
/**
|
||||
* Should we default the dropdown to the region determined from the user's locale?
|
||||
*
|
||||
* @config
|
||||
* @var bool
|
||||
*/
|
||||
private static $default_to_locale = true;
|
||||
|
||||
/**
|
||||
* The region code to default to if default_to_locale is set to false, or we can't determine a region from a locale
|
||||
* @var string
|
||||
* The region code to default to if default_to_locale is set to false, or we can't
|
||||
* determine a region from a locale.
|
||||
*
|
||||
* @config
|
||||
* @var string
|
||||
*/
|
||||
private static $default_country = 'NZ';
|
||||
|
||||
@ -28,48 +33,57 @@ class CountryDropdownField extends DropdownField {
|
||||
* @return string
|
||||
*/
|
||||
protected function locale() {
|
||||
if (($member = Member::currentUser()) && $member->Locale) return $member->Locale;
|
||||
if (($member = Member::currentUser()) && $member->Locale) {
|
||||
return $member->Locale;
|
||||
}
|
||||
return i18n::get_locale();
|
||||
}
|
||||
|
||||
public function __construct($name, $title = null, $source = null, $value = "", $form=null) {
|
||||
if(!is_array($source)) {
|
||||
// Get a list of countries from Zend
|
||||
$source = Zend_Locale::getTranslationList('territory', $this->locale(), 2);
|
||||
|
||||
// We want them ordered by display name, not country code
|
||||
|
||||
// PHP 5.3 has an extension that sorts UTF-8 strings correctly
|
||||
if (class_exists('Collator') && ($collator = Collator::create($this->locale()))) {
|
||||
$collator->asort($source);
|
||||
}
|
||||
// Otherwise just put up with them being weirdly ordered for now
|
||||
else {
|
||||
asort($source);
|
||||
}
|
||||
|
||||
// We don't want "unknown country" as an option
|
||||
unset($source['ZZ']);
|
||||
public function setSource($source) {
|
||||
if($source) {
|
||||
return parent::setSource($source);
|
||||
}
|
||||
|
||||
parent::__construct($name, ($title===null) ? $name : $title, $source, $value, $form);
|
||||
// map empty source to country list
|
||||
// Get a list of countries from Zend
|
||||
$source = Zend_Locale::getTranslationList('territory', $this->locale(), 2);
|
||||
|
||||
// We want them ordered by display name, not country code
|
||||
|
||||
// PHP 5.3 has an extension that sorts UTF-8 strings correctly
|
||||
if (class_exists('Collator') && ($collator = Collator::create($this->locale()))) {
|
||||
$collator->asort($source);
|
||||
} else {
|
||||
// Otherwise just put up with them being weirdly ordered for now
|
||||
asort($source);
|
||||
}
|
||||
|
||||
// We don't want "unknown country" as an option
|
||||
unset($source['ZZ']);
|
||||
|
||||
return parent::setSource($source);
|
||||
}
|
||||
|
||||
public function Field($properties = array()) {
|
||||
$source = $this->getSource();
|
||||
|
||||
if (!$this->value || !isset($source[$this->value])) {
|
||||
if ($this->config()->default_to_locale && $this->locale()) {
|
||||
$locale = new Zend_Locale();
|
||||
$locale->setLocale($this->locale());
|
||||
$this->value = $locale->getRegion();
|
||||
}
|
||||
// Default value to best availabel locale
|
||||
$value = $this->Value();
|
||||
if ($this->config()->default_to_locale
|
||||
&& (!$value || !isset($source[$value]))
|
||||
&& $this->locale()
|
||||
) {
|
||||
$locale = new Zend_Locale();
|
||||
$locale->setLocale($this->locale());
|
||||
$value = $locale->getRegion();
|
||||
$this->setValue($value);
|
||||
}
|
||||
|
||||
if (!$this->value || !isset($source[$this->value])) {
|
||||
$this->value = $this->config()->default_country;
|
||||
// Default to default country otherwise
|
||||
if (!$value || !isset($source[$value])) {
|
||||
$this->setValue($this->config()->default_country);
|
||||
}
|
||||
|
||||
return parent::Field();
|
||||
return parent::Field($properties);
|
||||
}
|
||||
}
|
||||
|
@ -81,51 +81,31 @@
|
||||
* @package forms
|
||||
* @subpackage fields-basic
|
||||
*/
|
||||
class DropdownField extends FormField {
|
||||
class DropdownField extends SingleSelectField {
|
||||
|
||||
/**
|
||||
* @var array|ArrayAccess $source Associative or numeric array of all dropdown items,
|
||||
* with array key as the submitted field value, and the array value as a
|
||||
* natural language description shown in the interface element.
|
||||
* Build a field option for template rendering
|
||||
*
|
||||
* @param mixed $value Value of the option
|
||||
* @param string $title Title of the option
|
||||
* @return ArrayData Field option
|
||||
*/
|
||||
protected $source;
|
||||
protected function getFieldOption($value, $title) {
|
||||
// Check selection
|
||||
$selected = $this->isSelectedValue($value, $this->Value());
|
||||
|
||||
/**
|
||||
* @var boolean $isSelected Determines if the field was selected
|
||||
* at the time it was rendered, so if {@link $value} matches on of the array
|
||||
* values specified in {@link $source}
|
||||
*/
|
||||
protected $isSelected;
|
||||
// Check disabled
|
||||
$disabled = false;
|
||||
if(in_array($value, $this->getDisabledItems()) && $title != $this->getEmptyString()){
|
||||
$disabled = 'disabled';
|
||||
}
|
||||
|
||||
/**
|
||||
* @var boolean $hasEmptyDefault Show the first <option> element as
|
||||
* empty (not having a value), with an optional label defined through
|
||||
* {@link $emptyString}. By default, the <select> element will be
|
||||
* rendered with the first option from {@link $source} selected.
|
||||
*/
|
||||
protected $hasEmptyDefault = false;
|
||||
|
||||
/**
|
||||
* @var string $emptyString The title shown for an empty default selection,
|
||||
* e.g. "Select...".
|
||||
*/
|
||||
protected $emptyString = '';
|
||||
|
||||
/**
|
||||
* @var array $disabledItems The keys for items that should be disabled (greyed out) in the dropdown
|
||||
*/
|
||||
protected $disabledItems = array();
|
||||
|
||||
/**
|
||||
* @param string $name The field name
|
||||
* @param string $title The field title
|
||||
* @param array|ArrayAccess $source A map of the dropdown items
|
||||
* @param string $value The current value
|
||||
* @param Form $form The parent form
|
||||
*/
|
||||
public function __construct($name, $title=null, $source=array(), $value='', $form=null) {
|
||||
$this->setSource($source);
|
||||
parent::__construct($name, ($title===null) ? $name : $title, $value, $form);
|
||||
return new ArrayData(array(
|
||||
'Title' => $title,
|
||||
'Value' => $value,
|
||||
'Selected' => $selected,
|
||||
'Disabled' => $disabled,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,49 +113,11 @@ class DropdownField extends FormField {
|
||||
* @return HTMLText
|
||||
*/
|
||||
public function Field($properties = array()) {
|
||||
$source = $this->getSource();
|
||||
$options = array();
|
||||
|
||||
if ($this->getHasEmptyDefault()) {
|
||||
$selected = ($this->value === '' || $this->value === null);
|
||||
$disabled = (in_array('', $this->disabledItems, true)) ? 'disabled' : false;
|
||||
|
||||
$options[] = new ArrayData(array(
|
||||
'Value' => '',
|
||||
'Title' => $this->getEmptyString(),
|
||||
'Selected' => $selected,
|
||||
'Disabled' => $disabled
|
||||
));
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
foreach($source as $value => $title) {
|
||||
$selected = false;
|
||||
if($value === '' && ($this->value === '' || $this->value === null)) {
|
||||
$selected = true;
|
||||
} else {
|
||||
// check against value, fallback to a type check comparison when !value
|
||||
if($value) {
|
||||
$selected = ($value == $this->value);
|
||||
} else {
|
||||
$selected = ($value === $this->value) || (((string) $value) === ((string) $this->value));
|
||||
}
|
||||
|
||||
$this->isSelected = $selected;
|
||||
}
|
||||
|
||||
$disabled = false;
|
||||
if(in_array($value, $this->disabledItems) && $title != $this->emptyString ){
|
||||
$disabled = 'disabled';
|
||||
}
|
||||
|
||||
$options[] = new ArrayData(array(
|
||||
'Title' => $title,
|
||||
'Value' => $value,
|
||||
'Selected' => $selected,
|
||||
'Disabled' => $disabled,
|
||||
));
|
||||
}
|
||||
// Add all options
|
||||
foreach($this->getSourceEmpty() as $value => $title) {
|
||||
$options[] = $this->getFieldOption($value, $title);
|
||||
}
|
||||
|
||||
$properties = array_merge($properties, array(
|
||||
@ -184,173 +126,4 @@ class DropdownField extends FormField {
|
||||
|
||||
return parent::Field($properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark certain elements as disabled, regardless of the
|
||||
* {@link setDisabled()} settings.
|
||||
*
|
||||
* @param array $items Collection of array keys, as defined in the $source array
|
||||
*/
|
||||
public function setDisabledItems($items) {
|
||||
$this->disabledItems = $items;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDisabledItems() {
|
||||
return $this->disabledItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes() {
|
||||
return array_merge(
|
||||
parent::getAttributes(),
|
||||
array(
|
||||
'type' => null,
|
||||
'value' => null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSelected() {
|
||||
return $this->isSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source array including any empty default values.
|
||||
*
|
||||
* @return array|ArrayAccess
|
||||
*/
|
||||
public function getSource() {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|ArrayAccess $source
|
||||
*/
|
||||
public function setSource($source) {
|
||||
$this->source = $source;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $bool
|
||||
*/
|
||||
public function setHasEmptyDefault($bool) {
|
||||
$this->hasEmptyDefault = $bool;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getHasEmptyDefault() {
|
||||
return $this->hasEmptyDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default selection label, e.g. "select...".
|
||||
*
|
||||
* Defaults to an empty string. Automatically sets {@link $hasEmptyDefault}
|
||||
* to true.
|
||||
*
|
||||
* @param string $str
|
||||
*/
|
||||
public function setEmptyString($str) {
|
||||
$this->setHasEmptyDefault(true);
|
||||
$this->emptyString = $str;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmptyString() {
|
||||
return $this->emptyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return LookupField
|
||||
*/
|
||||
public function performReadonlyTransformation() {
|
||||
$field = $this->castedCopy('LookupField');
|
||||
$field->setSource($this->getSource());
|
||||
$field->setReadonly(true);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of this field as an array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSourceAsArray()
|
||||
{
|
||||
$source = $this->getSource();
|
||||
if (is_array($source)) {
|
||||
return $source;
|
||||
} else {
|
||||
$sourceArray = array();
|
||||
foreach ($source as $key => $value) {
|
||||
$sourceArray[$key] = $value;
|
||||
}
|
||||
}
|
||||
return $sourceArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$source = $this->getSourceAsArray();
|
||||
$disabled = $this->getDisabledItems();
|
||||
|
||||
if (!array_key_exists($this->value, $source) || in_array($this->value, $disabled)) {
|
||||
if ($this->getHasEmptyDefault() && !$this->value) {
|
||||
return true;
|
||||
}
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DropdownField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. {value} is not a valid option",
|
||||
array('value' => $this->value)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns another instance of this field, but "cast" to a different class.
|
||||
*
|
||||
* @see FormField::castedCopy()
|
||||
*
|
||||
* @param String $classOrCopy
|
||||
* @return FormField
|
||||
*/
|
||||
public function castedCopy($classOrCopy) {
|
||||
$field = parent::castedCopy($classOrCopy);
|
||||
if($field->hasMethod('setHasEmptyDefault')) {
|
||||
$field->setHasEmptyDefault($this->getHasEmptyDefault());
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
|
@ -649,7 +649,6 @@ class FormField extends RequestHandler {
|
||||
*/
|
||||
public function setValue($value) {
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -36,18 +36,13 @@
|
||||
*
|
||||
* <b>Disabling individual items</b>
|
||||
*
|
||||
* Unlike the source, disabled items are specified in the same way as
|
||||
* normal DropdownFields, using a single value list. Don't pass in grouped
|
||||
* values here.
|
||||
*
|
||||
* <code>
|
||||
* $groupedDrDownField->setDisabledItems(
|
||||
* array(
|
||||
* "numbers" => array(
|
||||
* "1" => "1",
|
||||
* "3" => "3"
|
||||
* ),
|
||||
* "letters" => array(
|
||||
* "3" => "C"
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* // Disables first and third option in each group
|
||||
* $groupedDrDownField->setDisabledItems(array("1", "3"))
|
||||
* </code>
|
||||
*
|
||||
* @package forms
|
||||
@ -55,80 +50,47 @@
|
||||
*/
|
||||
class GroupedDropdownField extends DropdownField {
|
||||
|
||||
public function Field($properties = array()) {
|
||||
$options = '';
|
||||
foreach($this->getSource() as $value => $title) {
|
||||
if(is_array($title)) {
|
||||
$options .= "<optgroup label=\"$value\">";
|
||||
foreach($title as $value2 => $title2) {
|
||||
$disabled = '';
|
||||
if( array_key_exists($value, $this->disabledItems)
|
||||
&& is_array($this->disabledItems[$value])
|
||||
&& in_array($value2, $this->disabledItems[$value]) ){
|
||||
$disabled = 'disabled="disabled"';
|
||||
}
|
||||
$selected = $value2 == $this->value ? " selected=\"selected\"" : "";
|
||||
$options .= "<option$selected value=\"$value2\" $disabled>$title2</option>";
|
||||
}
|
||||
$options .= "</optgroup>";
|
||||
} else { // Fall back to the standard dropdown field
|
||||
$disabled = '';
|
||||
if( in_array($value, $this->disabledItems) ){
|
||||
$disabled = 'disabled="disabled"';
|
||||
}
|
||||
$selected = $value == $this->value ? " selected=\"selected\"" : "";
|
||||
$options .= "<option$selected value=\"$value\" $disabled>$title</option>";
|
||||
}
|
||||
/**
|
||||
* Build a potentially nested fieldgroup
|
||||
*
|
||||
* @param mixed $valueOrGroup Value of item, or title of group
|
||||
* @param string|array $titleOrOptions Title of item, or options in grouip
|
||||
* @return ArrayData Data for this item
|
||||
*/
|
||||
protected function getFieldOption($valueOrGroup, $titleOrOptions) {
|
||||
// Return flat option
|
||||
if(!is_array($titleOrOptions)) {
|
||||
return parent::getFieldOption($valueOrGroup, $titleOrOptions);
|
||||
}
|
||||
|
||||
return FormField::create_tag('select', $this->getAttributes(), $options);
|
||||
// Build children from options list
|
||||
$options = new ArrayList();
|
||||
foreach($titleOrOptions as $childValue => $childTitle) {
|
||||
$options->push($this->getFieldOption($childValue, $childTitle));
|
||||
}
|
||||
|
||||
return new ArrayData(array(
|
||||
'Title' => $valueOrGroup,
|
||||
'Options' => $options
|
||||
));
|
||||
}
|
||||
|
||||
public function Type() {
|
||||
return 'groupeddropdown dropdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$valid = false;
|
||||
$source = $this->getSourceAsArray();
|
||||
$disabled = $this->getDisabledItems();
|
||||
|
||||
if ($this->value) {
|
||||
foreach ($source as $value => $title) {
|
||||
if (is_array($title) && array_key_exists($this->value, $title)) {
|
||||
// Check that the set value is not in the list of disabled items
|
||||
if (!isset($disabled[$value]) || !in_array($this->value, $disabled[$value])) {
|
||||
$valid = true;
|
||||
}
|
||||
// Check that the value matches and is not disabled
|
||||
} elseif($this->value == $value && !in_array($this->value, $disabled)) {
|
||||
$valid = true;
|
||||
}
|
||||
public function getSourceValues() {
|
||||
// Flatten values
|
||||
$values = array();
|
||||
$source = $this->getSource();
|
||||
array_walk_recursive(
|
||||
$source,
|
||||
// Function to extract value from array key
|
||||
function($title, $value) use (&$values) {
|
||||
$values[] = $value;
|
||||
}
|
||||
} elseif ($this->getHasEmptyDefault()) {
|
||||
$valid = true;
|
||||
}
|
||||
|
||||
if (!$valid) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DropdownField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. {value} is not a valid option",
|
||||
array('value' => $this->value)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
);
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
* @package forms
|
||||
* @subpackage fields-basic
|
||||
*/
|
||||
class ListboxField extends DropdownField {
|
||||
class ListboxField extends MultiSelectField {
|
||||
|
||||
/**
|
||||
* The size of the field in rows.
|
||||
@ -33,223 +33,107 @@ class ListboxField extends DropdownField {
|
||||
*/
|
||||
protected $size;
|
||||
|
||||
/**
|
||||
* Should the user be able to select multiple
|
||||
* items on this dropdown field?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $multiple = false;
|
||||
|
||||
/**
|
||||
* @var Array
|
||||
*/
|
||||
protected $disabledItems = array();
|
||||
|
||||
/**
|
||||
* @var Array
|
||||
*/
|
||||
protected $defaultItems = array();
|
||||
|
||||
/**
|
||||
* Creates a new dropdown field.
|
||||
*
|
||||
* @param string $name The field name
|
||||
* @param string $title The field title
|
||||
* @param array $source An map of the dropdown items
|
||||
* @param string|array $value You can pass an array of values or a single value like a drop down to be selected
|
||||
* @param string|array|null $value You can pass an array of values or a single value like a drop down to be selected
|
||||
* @param int $size Optional size of the select element
|
||||
* @param form The parent form
|
||||
*/
|
||||
public function __construct($name, $title = '', $source = array(), $value = '', $size = null, $multiple = false) {
|
||||
if($size) $this->size = $size;
|
||||
if($multiple) $this->multiple = $multiple;
|
||||
public function __construct($name, $title = '', $source = array(), $value = null, $size = null) {
|
||||
if($size) {
|
||||
$this->setSize($size);
|
||||
}
|
||||
|
||||
parent::__construct($name, $title, $source, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <select> tag containing all the appropriate <option> tags
|
||||
*
|
||||
* @param array $properties
|
||||
* @return string
|
||||
*/
|
||||
public function Field($properties = array()) {
|
||||
if($this->multiple) $this->name .= '[]';
|
||||
$properties = array_merge($properties, array(
|
||||
'Options' => $this->getOptions()
|
||||
));
|
||||
return $this
|
||||
->customise($properties)
|
||||
->renderWith($this->getTemplates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of options to render in this formfield
|
||||
*
|
||||
* @return ArrayList
|
||||
*/
|
||||
public function getOptions() {
|
||||
// Loop through and figure out which values were selected.
|
||||
$options = array();
|
||||
|
||||
// We have an array of values
|
||||
if(is_array($this->value)){
|
||||
// Loop through and figure out which values were selected.
|
||||
foreach($this->getSource() as $value => $title) {
|
||||
$options[] = new ArrayData(array(
|
||||
'Title' => $title,
|
||||
'Value' => $value,
|
||||
'Selected' => (in_array($value, $this->value) || in_array($value, $this->defaultItems)),
|
||||
'Disabled' => $this->disabled || in_array($value, $this->disabledItems),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// Listbox was based a singlular value, so treat it like a dropdown.
|
||||
foreach($this->getSource() as $value => $title) {
|
||||
$options[] = new ArrayData(array(
|
||||
'Title' => $title,
|
||||
'Value' => $value,
|
||||
'Selected' => ($value == $this->value || in_array($value, $this->defaultItems)),
|
||||
'Disabled' => $this->disabled || in_array($value, $this->disabledItems),
|
||||
));
|
||||
}
|
||||
$selectedValue = $this->getValueArray();
|
||||
foreach($this->getSource() as $itemValue => $title) {
|
||||
$itemSelected = in_array($itemValue, $selectedValue)
|
||||
|| in_array($itemValue, $this->getDefaultItems());
|
||||
$itemDisabled = $this->isDisabled()
|
||||
|| in_array($itemValue, $this->getDisabledItems());
|
||||
$options[] = new ArrayData(array(
|
||||
'Title' => $title,
|
||||
'Value' => $itemValue,
|
||||
'Selected' => $itemSelected,
|
||||
'Disabled' => $itemDisabled,
|
||||
));
|
||||
}
|
||||
|
||||
$properties = array_merge($properties, array(
|
||||
'Options' => new ArrayList($options)
|
||||
));
|
||||
|
||||
return $this->customise($properties)->renderWith($this->getTemplates());
|
||||
$options = new ArrayList($options);
|
||||
$this->extend('updateGetOptions', $options);
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function getAttributes() {
|
||||
return array_merge(
|
||||
parent::getAttributes(),
|
||||
array(
|
||||
'multiple' => $this->multiple,
|
||||
'size' => $this->size
|
||||
'multiple' => 'true',
|
||||
'size' => $this->getSize(),
|
||||
'name' => $this->getName() . '[]'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this dropdown in rows.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getSize() {
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of this dropdown in rows.
|
||||
*
|
||||
* @param int $size The height in rows (e.g. 3)
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function setSize($size) {
|
||||
$this->size = $size;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this field to have a muliple select attribute
|
||||
* @param boolean $bool
|
||||
*/
|
||||
public function setMultiple($bool) {
|
||||
$this->multiple = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSource($source) {
|
||||
if($source) {
|
||||
$hasCommas = array_filter(array_keys($source),
|
||||
create_function('$key', 'return strpos($key, ",") !== FALSE;'));
|
||||
if($hasCommas) {
|
||||
throw new InvalidArgumentException('No commas allowed in $source keys');
|
||||
}
|
||||
}
|
||||
|
||||
parent::setSource($source);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the CheckboxSetField value as a string
|
||||
* selected item keys.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dataValue() {
|
||||
if($this->value && is_array($this->value) && $this->multiple) {
|
||||
$filtered = array();
|
||||
foreach($this->value as $item) {
|
||||
if($item) {
|
||||
$filtered[] = str_replace(",", "{comma}", $item);
|
||||
}
|
||||
}
|
||||
return implode(',', $filtered);
|
||||
} else {
|
||||
return parent::dataValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current value of this field into a DataObject.
|
||||
* If the field it is saving to is a has_many or many_many relationship,
|
||||
* it is saved by setByIDList(), otherwise it creates a comma separated
|
||||
* list for a standard DB text/varchar field.
|
||||
*
|
||||
* @param DataObject $record The record to save into
|
||||
*/
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
if($this->multiple) {
|
||||
$fieldname = $this->name;
|
||||
$relation = ($fieldname && $record && $record->hasMethod($fieldname)) ? $record->$fieldname() : null;
|
||||
if($fieldname && $record && $relation &&
|
||||
($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||
$idList = (is_array($this->value)) ? array_values($this->value) : array();
|
||||
if(!$record->ID) {
|
||||
$record->write(); // record needs to have an ID in order to set relationships
|
||||
$relation = ($fieldname && $record && $record->hasMethod($fieldname))
|
||||
? $record->$fieldname()
|
||||
: null;
|
||||
}
|
||||
$relation->setByIDList($idList);
|
||||
} elseif($fieldname && $record) {
|
||||
if($this->value) {
|
||||
$this->value = str_replace(',', '{comma}', $this->value);
|
||||
$record->$fieldname = implode(",", $this->value);
|
||||
} else {
|
||||
$record->$fieldname = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parent::saveInto($record);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value into this ListboxField
|
||||
*/
|
||||
public function setValue($val, $obj = null) {
|
||||
// If we're not passed a value directly,
|
||||
// we can look for it in a relation method on the object passed as a second arg
|
||||
if(!$val && $obj && $obj instanceof DataObject && $obj->hasMethod($this->name)) {
|
||||
$funcName = $this->name;
|
||||
$val = array_values($obj->$funcName()->getIDList());
|
||||
}
|
||||
|
||||
if($val) {
|
||||
if(!$this->multiple && is_array($val)) {
|
||||
throw new InvalidArgumentException('Array values are not allowed (when multiple=false).');
|
||||
}
|
||||
|
||||
if($this->multiple) {
|
||||
$parts = (is_array($val)) ? $val : preg_split("/ *, */", trim($val));
|
||||
if(ArrayLib::is_associative($parts)) {
|
||||
// This is due to the possibility of accidentally passing an array of values (as keys) and titles (as values) when only the keys were intended to be saved.
|
||||
throw new InvalidArgumentException('Associative arrays are not allowed as values (when multiple=true), only indexed arrays.');
|
||||
}
|
||||
|
||||
// Doesn't check against unknown values in order to allow for less rigid data handling.
|
||||
// They're silently ignored and overwritten the next time the field is saved.
|
||||
parent::setValue($parts);
|
||||
} else {
|
||||
if(!in_array($val, array_keys($this->getSource()))) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid value "%s" for multiple=false',
|
||||
Convert::raw2xml($val)
|
||||
));
|
||||
}
|
||||
|
||||
parent::setValue($val);
|
||||
}
|
||||
} else {
|
||||
parent::setValue($val);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark certain elements as disabled,
|
||||
* regardless of the {@link setDisabled()} settings.
|
||||
*
|
||||
* @param array $items Collection of array keys, as defined in the $source array
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function setDisabledItems($items) {
|
||||
$this->disabledItems = $items;
|
||||
@ -257,70 +141,10 @@ class ListboxField extends DropdownField {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array
|
||||
* @return array
|
||||
*/
|
||||
public function getDisabledItems() {
|
||||
return $this->disabledItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default selections, regardless of the {@link setValue()} settings.
|
||||
* Note: Items marked as disabled through {@link setDisabledItems()} can still be
|
||||
* selected by default through this method.
|
||||
*
|
||||
* @param Array $items Collection of array keys, as defined in the $source array
|
||||
*/
|
||||
public function setDefaultItems($items) {
|
||||
$this->defaultItems = $items;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Array
|
||||
*/
|
||||
public function getDefaultItems() {
|
||||
return $this->defaultItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$values = $this->value;
|
||||
if (!$values) {
|
||||
return true;
|
||||
}
|
||||
$source = $this->getSourceAsArray();
|
||||
if (is_array($values)) {
|
||||
if (!array_intersect_key($source,array_flip($values))) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
"Please select a value within the list provided. {value} is not a valid option",
|
||||
array('value' => $this->value)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!array_key_exists($this->value, $source)) {
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'ListboxField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. %s is not a valid option",
|
||||
array('value' => $this->value)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
* @package forms
|
||||
* @subpackage fields-basic
|
||||
*/
|
||||
class LookupField extends DropdownField {
|
||||
class LookupField extends MultiSelectField {
|
||||
|
||||
/**
|
||||
* @var boolean $readonly
|
||||
@ -24,36 +24,20 @@ class LookupField extends DropdownField {
|
||||
* @return string
|
||||
*/
|
||||
public function Field($properties = array()) {
|
||||
$source = $this->getSource();
|
||||
|
||||
// Normalize value to array to simplify further processing
|
||||
if(is_array($this->value) || is_object($this->value)) {
|
||||
$values = $this->value;
|
||||
} else {
|
||||
$values = array(trim($this->value));
|
||||
}
|
||||
$source = ArrayLib::flatten($this->getSource());
|
||||
$values = $this->getValueArray();
|
||||
|
||||
// Get selected values
|
||||
$mapped = array();
|
||||
|
||||
if($source instanceof SQLMap) {
|
||||
foreach($values as $value) {
|
||||
$mapped[] = $source->getItem($value);
|
||||
foreach($values as $value) {
|
||||
if(isset($source[$value])) {
|
||||
$mapped[] = $source[$value];
|
||||
}
|
||||
} else if($source instanceof ArrayAccess || is_array($source)) {
|
||||
$source = ArrayLib::flatten($source);
|
||||
|
||||
foreach($values as $value) {
|
||||
if(isset($source[$value])) {
|
||||
$mapped[] = $source[$value];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$mapped = array();
|
||||
}
|
||||
|
||||
// Don't check if string arguments are matching against the source,
|
||||
// as they might be generated HTML diff views instead of the actual values
|
||||
if($this->value && !is_array($this->value) && !$mapped) {
|
||||
if($this->value && is_string($this->value) && empty($mapped)) {
|
||||
$mapped = array(trim($this->value));
|
||||
$values = array();
|
||||
}
|
||||
@ -102,10 +86,13 @@ class LookupField extends DropdownField {
|
||||
*/
|
||||
public function performReadonlyTransformation() {
|
||||
$clone = clone $this;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function getHasEmptyDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
@ -5,65 +5,69 @@
|
||||
*/
|
||||
class MemberDatetimeOptionsetField extends OptionsetField {
|
||||
|
||||
const CUSTOM_OPTION = '__custom__';
|
||||
|
||||
public function Field($properties = array()) {
|
||||
Requirements::javascript(FRAMEWORK_ADMIN_DIR . '/javascript/MemberDatetimeOptionsetField.js');
|
||||
$options = array();
|
||||
$odd = false;
|
||||
|
||||
$options = '';
|
||||
$odd = 0;
|
||||
$source = $this->getSource();
|
||||
|
||||
foreach($source as $key => $value) {
|
||||
// convert the ID to an HTML safe value (dots are not replaced, as they are valid in an ID attribute)
|
||||
$itemID = $this->id() . '_' . preg_replace('/[^\.a-zA-Z0-9\-\_]/', '_', $key);
|
||||
if($key == $this->value) {
|
||||
$useValue = false;
|
||||
$checked = " checked=\"checked\"";
|
||||
} else {
|
||||
$checked = "";
|
||||
// Add all options striped
|
||||
$anySelected = false;
|
||||
foreach($this->getSourceEmpty() as $value => $title) {
|
||||
$odd = !$odd;
|
||||
if(!$anySelected) {
|
||||
$anySelected = $this->isSelectedValue($value, $this->Value());
|
||||
}
|
||||
|
||||
$odd = ($odd + 1) % 2;
|
||||
$extraClass = $odd ? "odd" : "even";
|
||||
$extraClass .= " val" . preg_replace('/[^a-zA-Z0-9\-\_]/', '_', $key);
|
||||
$disabled = ($this->disabled || in_array($key, $this->disabledItems)) ? "disabled=\"disabled\"" : "";
|
||||
$ATT_key = Convert::raw2att($key);
|
||||
|
||||
$options .= "<li class=\"".$extraClass."\">"
|
||||
. "<input id=\"$itemID\" name=\"$this->name\" type=\"radio\" value=\"$key\"$checked $disabled"
|
||||
. " class=\"radio\" /> <label title=\"$ATT_key\" for=\"$itemID\">$value</label></li>\n";
|
||||
$options[] = $this->getFieldOption($value, $title, $odd);
|
||||
}
|
||||
|
||||
// Add "custom" input field
|
||||
$value = ($this->value && !array_key_exists($this->value, $this->source)) ? $this->value : null;
|
||||
$checked = ($value) ? " checked=\"checked\"" : '';
|
||||
$options .= "<li class=\"valCustom\">"
|
||||
. sprintf(
|
||||
"<input id=\"%s_custom\" name=\"%s\" type=\"radio\" value=\"__custom__\" class=\"radio\" %s />",
|
||||
$itemID, $this->name,
|
||||
$checked
|
||||
)
|
||||
. sprintf(
|
||||
'<label for="%s_custom">%s:</label>',
|
||||
$itemID, _t('MemberDatetimeOptionsetField.Custom', 'Custom')
|
||||
)
|
||||
. sprintf(
|
||||
"<input class=\"customFormat cms-help cms-help-tooltip\" name=\"%s_custom\" value=\"%s\" />\n",
|
||||
$this->name, Convert::raw2xml($value)
|
||||
)
|
||||
. sprintf(
|
||||
"<input type=\"hidden\" class=\"formatValidationURL\" value=\"%s\" />",
|
||||
$this->Link() . '/validate'
|
||||
);
|
||||
$options .= ($value) ? sprintf(
|
||||
'<span class="preview">(%s: "%s")</span>',
|
||||
_t('MemberDatetimeOptionsetField.Preview', 'Preview'),
|
||||
Convert::raw2xml(Zend_Date::now()->toString($value))
|
||||
) : '';
|
||||
// Add "custom" input field option
|
||||
$options[] = $this->getCustomFieldOption(!$anySelected, !$odd);
|
||||
|
||||
$id = $this->id();
|
||||
return "<ul id=\"$id\" class=\"optionset {$this->extraClass()}\">\n$options</ul>\n";
|
||||
// Build fieldset
|
||||
$properties = array_merge($properties, array(
|
||||
'Options' => new ArrayList($options)
|
||||
));
|
||||
|
||||
return $this->customise($properties)->renderWith(
|
||||
$this->getTemplates()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the "custom" selection field option
|
||||
*
|
||||
* @param bool $isChecked True if this is checked
|
||||
* @param bool $odd Is odd striped
|
||||
* @return ArrayData
|
||||
*/
|
||||
protected function getCustomFieldOption($isChecked, $odd) {
|
||||
// Add "custom" input field
|
||||
$option = $this->getFieldOption(
|
||||
self::CUSTOM_OPTION,
|
||||
_t('MemberDatetimeOptionsetField.Custom', 'Custom'),
|
||||
$odd
|
||||
);
|
||||
$option->setField('isChecked', $isChecked);
|
||||
$option->setField('CustomName', $this->getName().'[Custom]');
|
||||
$option->setField('CustomValue', $this->Value());
|
||||
if($this->Value()) {
|
||||
$preview = Convert::raw2xml(Zend_Date::now()->toString($this->Value()));
|
||||
$option->setField('CustomPreview', $preview);
|
||||
$option->setField('CustomPreviewLabel', _t('MemberDatetimeOptionsetField.Preview', 'Preview'));
|
||||
}
|
||||
return $option;
|
||||
}
|
||||
|
||||
public function getItemName() {
|
||||
return parent::getItemName() . '[Options]';
|
||||
}
|
||||
|
||||
public function Type() {
|
||||
return 'optionset memberdatetimeoptionset';
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Put this text into a template?
|
||||
*/
|
||||
@ -110,13 +114,20 @@ class MemberDatetimeOptionsetField extends OptionsetField {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
public function setValue($value) {
|
||||
if($value == '__custom__') {
|
||||
$value = isset($_REQUEST[$this->name . '_custom']) ? $_REQUEST[$this->name . '_custom'] : null;
|
||||
}
|
||||
if($value) {
|
||||
parent::setValue($value);
|
||||
// Extract custom option from postback
|
||||
if(is_array($value)) {
|
||||
if(empty($value['Options'])) {
|
||||
$value = '';
|
||||
} elseif($value['Options'] === self::CUSTOM_OPTION) {
|
||||
$value = $value['Custom'];
|
||||
} else {
|
||||
$value = $value['Options'];
|
||||
}
|
||||
}
|
||||
|
||||
return parent::setValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,8 +137,10 @@ class MemberDatetimeOptionsetField extends OptionsetField {
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$value = isset($_POST[$this->name . '_custom']) ? $_POST[$this->name . '_custom'] : null;
|
||||
if(!$value) return true; // no custom value, don't validate
|
||||
$value = $this->Value();
|
||||
if(!$value) {
|
||||
return true; // no custom value, don't validate
|
||||
}
|
||||
|
||||
// Check that the current date with the date format is valid or not
|
||||
require_once 'Zend/Date.php';
|
||||
@ -135,12 +148,17 @@ class MemberDatetimeOptionsetField extends OptionsetField {
|
||||
$valid = Zend_Date::isDate($date, $value);
|
||||
if($valid) {
|
||||
return true;
|
||||
} else {
|
||||
if($validator) {
|
||||
$validator->validationError($this->name,
|
||||
_t('MemberDatetimeOptionsetField.DATEFORMATBAD',"Date format is invalid"), "validation", false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail
|
||||
$validator->validationError(
|
||||
$this->getName(),
|
||||
_t(
|
||||
'MemberDatetimeOptionsetField.DATEFORMATBAD',
|
||||
"Date format is invalid"
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
221
forms/MultiSelectField.php
Normal file
221
forms/MultiSelectField.php
Normal file
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Model\Relation;
|
||||
|
||||
/**
|
||||
* Represents a SelectField that may potentially have multiple selections, and may have
|
||||
* a {@link ManyManyList} as a data source.
|
||||
*/
|
||||
abstract class MultiSelectField extends SelectField {
|
||||
|
||||
/**
|
||||
* List of items to mark as checked, and may not be unchecked
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultItems = array();
|
||||
|
||||
/**
|
||||
* Extracts the value of this field, normalised as an array.
|
||||
* Scalar values will return a single length array, even if empty
|
||||
*
|
||||
* @return array List of values as an array
|
||||
*/
|
||||
public function getValueArray() {
|
||||
return $this->getListValues($this->Value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default selections, regardless of the {@link setValue()} settings.
|
||||
* Note: Items marked as disabled through {@link setDisabledItems()} can still be
|
||||
* selected by default through this method.
|
||||
*
|
||||
* @param array $items Collection of array keys, as defined in the $source array
|
||||
* @return $this Self reference
|
||||
*/
|
||||
public function setDefaultItems($items) {
|
||||
$this->defaultItems = $this->getListValues($items);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default selections, regardless of the {@link setValue()} settings.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultItems() {
|
||||
return $this->defaultItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a value into this MultiSelectField
|
||||
*/
|
||||
public function setValue($val, $obj = null) {
|
||||
// If we're not passed a value directly,
|
||||
// we can look for it in a relation method on the object passed as a second arg
|
||||
if($obj instanceof DataObject) {
|
||||
$this->loadFrom($obj);
|
||||
} else {
|
||||
return parent::setValue($val);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the value from the dataobject into this field
|
||||
*
|
||||
* @param DataObjectInterface $record
|
||||
*/
|
||||
public function loadFrom(DataObjectInterface $record) {
|
||||
$fieldName = $this->getName();
|
||||
if(empty($fieldName) || empty($record)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relation = $record->hasMethod($fieldName)
|
||||
? $record->$fieldName()
|
||||
: null;
|
||||
|
||||
// Detect DB relation or field
|
||||
if($relation instanceof Relation) {
|
||||
// Load ids from relation
|
||||
$value = array_values($relation->getIDList());
|
||||
parent::setValue($value);
|
||||
} elseif($record->hasField($fieldName)) {
|
||||
$value = $this->stringDecode($record->$fieldName);
|
||||
parent::setValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the current value of this MultiSelectField into a DataObject.
|
||||
* If the field it is saving to is a has_many or many_many relationship,
|
||||
* it is saved by setByIDList(), otherwise it creates a comma separated
|
||||
* list for a standard DB text/varchar field.
|
||||
*
|
||||
* @param DataObject $record The record to save into
|
||||
*/
|
||||
public function saveInto(DataObjectInterface $record) {
|
||||
$fieldName = $this->getName();
|
||||
if(empty($fieldName) || empty($record)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$relation = $record->hasMethod($fieldName)
|
||||
? $record->$fieldName()
|
||||
: null;
|
||||
|
||||
// Detect DB relation or field
|
||||
$items = $this->getValueArray();
|
||||
if($relation instanceof Relation) {
|
||||
// Save ids into relation
|
||||
$relation->setByIDList($items);
|
||||
} elseif($record->hasField($fieldName)) {
|
||||
// Save dataValue into field
|
||||
$record->$fieldName = $this->stringEncode($items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a list of values into a string, or null if empty (to simplify empty checks)
|
||||
*
|
||||
* @param array $value
|
||||
* @return string|null
|
||||
*/
|
||||
public function stringEncode($value) {
|
||||
return $value
|
||||
? json_encode(array_values($value))
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a string value into an array of values
|
||||
*
|
||||
* @param string $value
|
||||
* @return array
|
||||
*/
|
||||
protected function stringDecode($value) {
|
||||
// Handle empty case
|
||||
if(empty($value)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// If json deserialisation fails, then fallover to legacy format
|
||||
$result = json_decode($value, true);
|
||||
if($result !== false) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Parse data in legacy {comma} format
|
||||
Deprecation::notice('4.0', 'Storing multi-selection values in comma separated format is deprecated');
|
||||
return str_replace('{comma}', ',', explode(',', $value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
$values = $this->getValueArray();
|
||||
$validValues = $this->getValidValues();
|
||||
|
||||
// Filter out selected values not in the data source
|
||||
$self = $this;
|
||||
$invalidValues = array_filter(
|
||||
$values,
|
||||
function($userValue) use ($self, $validValues) {
|
||||
foreach($validValues as $formValue) {
|
||||
if($self->isSelectedValue($formValue, $userValue)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
if(empty($invalidValues)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// List invalid items
|
||||
$validator->validationError(
|
||||
$this->getName(),
|
||||
_t(
|
||||
'MultiSelectField.SOURCE_VALIDATION',
|
||||
"Please select values within the list provided. Invalid option(s) {value} given",
|
||||
array('value' => implode(',', $invalidValues))
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the source data for this CheckboxSetField
|
||||
* into a comma separated list of values.
|
||||
*
|
||||
* @return ReadonlyField
|
||||
*/
|
||||
public function performReadonlyTransformation() {
|
||||
$source = $this->getSource();
|
||||
|
||||
// Map selected values to titles
|
||||
$data = array();
|
||||
foreach($this->getValueArray() as $value) {
|
||||
if(array_key_exists($value, $source)) {
|
||||
$data[] = $source[$value];
|
||||
} else {
|
||||
$data[] = $value;
|
||||
}
|
||||
}
|
||||
$values = implode(', ', $data);
|
||||
|
||||
$field = $this->castedCopy('ReadonlyField');
|
||||
$field->setValue($values);
|
||||
|
||||
return $field;
|
||||
}
|
||||
}
|
@ -50,33 +50,46 @@
|
||||
* @package forms
|
||||
* @subpackage fields-basic
|
||||
*/
|
||||
class OptionsetField extends DropdownField {
|
||||
class OptionsetField extends SingleSelectField {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Build a field option for template rendering
|
||||
*
|
||||
* @param mixed $value Value of the option
|
||||
* @param string $title Title of the option
|
||||
* @param boolean $odd True if this should be striped odd. Otherwise it should be striped even
|
||||
* @return ArrayData Field option
|
||||
*/
|
||||
protected function getFieldOption($value, $title, $odd) {
|
||||
// Check selection
|
||||
$selected = $this->isSelectedValue($value, $this->Value());
|
||||
$itemID = $this->ID() . '_' . Convert::raw2htmlid($value);
|
||||
$extraClass = $odd ? 'odd' : 'even';
|
||||
$extraClass .= ' val' . Convert::raw2htmlid($value);
|
||||
|
||||
return new ArrayData(array(
|
||||
'ID' => $itemID,
|
||||
'Class' => $extraClass,
|
||||
'Name' => $this->getItemName(),
|
||||
'Value' => $value,
|
||||
'Title' => $title,
|
||||
'isChecked' => $selected,
|
||||
'isDisabled' => $this->isDisabled() || in_array($value, $this->getDisabledItems()),
|
||||
));
|
||||
}
|
||||
|
||||
protected function getItemName() {
|
||||
return $this->getName();
|
||||
}
|
||||
|
||||
public function Field($properties = array()) {
|
||||
$source = $this->getSource();
|
||||
$odd = 0;
|
||||
$options = array();
|
||||
|
||||
if($source) {
|
||||
foreach($source as $value => $title) {
|
||||
$itemID = $this->ID() . '_' . preg_replace('/[^a-zA-Z0-9]/', '', $value);
|
||||
$odd = ($odd + 1) % 2;
|
||||
$extraClass = $odd ? 'odd' : 'even';
|
||||
$extraClass .= ' val' . preg_replace('/[^a-zA-Z0-9\-\_]/', '_', $value);
|
||||
|
||||
$options[] = new ArrayData(array(
|
||||
'ID' => $itemID,
|
||||
'Class' => $extraClass,
|
||||
'Name' => $this->name,
|
||||
'Value' => $value,
|
||||
'Title' => $title,
|
||||
'isChecked' => $value == $this->value,
|
||||
'isDisabled' => $this->disabled || in_array($value, $this->disabledItems),
|
||||
));
|
||||
}
|
||||
$odd = false;
|
||||
|
||||
// Add all options striped
|
||||
foreach($this->getSourceEmpty() as $value => $title) {
|
||||
$odd = !$odd;
|
||||
$options[] = $this->getFieldOption($value, $title, $odd);
|
||||
}
|
||||
|
||||
$properties = array_merge($properties, array(
|
||||
@ -92,23 +105,19 @@ class OptionsetField extends DropdownField {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($validator) {
|
||||
if (!$this->value) {
|
||||
if (!$this->Value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parent::validate($validator);
|
||||
}
|
||||
|
||||
public function ExtraOptions() {
|
||||
return new ArrayList();
|
||||
}
|
||||
|
||||
public function getAttributes() {
|
||||
$attributes = parent::getAttributes();
|
||||
unset($attributes['name']);
|
||||
unset($attributes['required']);
|
||||
unset($attributes['role']);
|
||||
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
}
|
||||
|
212
forms/SelectField.php
Normal file
212
forms/SelectField.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Represents a field that allows users to select one or more items from a list
|
||||
*/
|
||||
abstract class SelectField extends FormField {
|
||||
|
||||
/**
|
||||
* Associative or numeric array of all dropdown items,
|
||||
* with array key as the submitted field value, and the array value as a
|
||||
* natural language description shown in the interface element.
|
||||
*
|
||||
* @var array|ArrayAccess
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The values for items that should be disabled (greyed out) in the dropdown.
|
||||
* This is a non-associative array
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $disabledItems = array();
|
||||
|
||||
/**
|
||||
* @param string $name The field name
|
||||
* @param string $title The field title
|
||||
* @param array|ArrayAccess $source A map of the dropdown items
|
||||
* @param mixed $value The current value
|
||||
*/
|
||||
public function __construct($name, $title = null, $source = array(), $value = null) {
|
||||
$this->setSource($source);
|
||||
if(!isset($title)) {
|
||||
$title = $name;
|
||||
}
|
||||
parent::__construct($name, $title, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark certain elements as disabled,
|
||||
* regardless of the {@link setDisabled()} settings.
|
||||
*
|
||||
* These should be items that appear in the source list, not in addition to them.
|
||||
*
|
||||
* @param array|SS_List $items Collection of values or items
|
||||
*/
|
||||
public function setDisabledItems($items){
|
||||
$this->disabledItems = $this->getListValues($items);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-associative list of disabled item values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDisabledItems(){
|
||||
return $this->disabledItems;
|
||||
}
|
||||
|
||||
public function getAttributes() {
|
||||
return array_merge(
|
||||
parent::getAttributes(),
|
||||
array('type' => null, 'value' => null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all values in the source array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getSourceValues() {
|
||||
return array_keys($this->getSource());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all valid values for this field.
|
||||
*
|
||||
* Does not include "empty" value if specified
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getValidValues() {
|
||||
$valid = array_diff($this->getSourceValues(), $this->getDisabledItems());
|
||||
// Renumber indexes from 0
|
||||
return array_values($valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source array not including any empty default values.
|
||||
*
|
||||
* @return array|ArrayAccess
|
||||
*/
|
||||
public function getSource() {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source for this list
|
||||
*
|
||||
* @param mixed $source
|
||||
* @return $this
|
||||
*/
|
||||
public function setSource($source) {
|
||||
$this->source = $this->getListMap($source);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of values, extract the associative map of id => title
|
||||
*
|
||||
* @param mixed $source
|
||||
* @return array Associative array of ids and titles
|
||||
*/
|
||||
protected function getListMap($source) {
|
||||
// Extract source as an array
|
||||
if($source instanceof SS_List) {
|
||||
$source = $source->map();
|
||||
}
|
||||
if($source instanceof SS_Map) {
|
||||
$source = $source->toArray();
|
||||
}
|
||||
if(!is_array($source) && !($source instanceof ArrayAccess)) {
|
||||
user_error('$source passed in as invalid type', E_USER_ERROR);
|
||||
}
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a non-array collection, extract the non-associative list of ids
|
||||
* If passing as array, treat the array values (not the keys) as the ids
|
||||
*
|
||||
* @param mixed $values
|
||||
* @return array Non-associative list of values
|
||||
*/
|
||||
protected function getListValues($values) {
|
||||
// Empty values
|
||||
if(empty($values)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Direct array
|
||||
if(is_array($values)) {
|
||||
return array_values($values);
|
||||
}
|
||||
|
||||
// Extract lists
|
||||
if($values instanceof SS_List) {
|
||||
return $values->column('ID');
|
||||
}
|
||||
|
||||
return array(trim($values));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current value of this field matches the given option value
|
||||
*
|
||||
* @param mixed $dataValue The value as extracted from the source of this field (or empty value if available)
|
||||
* @param mixed $userValue The value as submitted by the user
|
||||
* @return boolean True if the selected value matches the given option value
|
||||
*/
|
||||
public function isSelectedValue($dataValue, $userValue) {
|
||||
if($dataValue === $userValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow null to match empty strings
|
||||
if($dataValue === '' && $userValue === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For non-falsey values do loose comparison
|
||||
if($dataValue) {
|
||||
return $dataValue == $userValue;
|
||||
}
|
||||
|
||||
// For empty values, use string comparison to perform visible value match
|
||||
return ((string) $dataValue) === ((string) $userValue);
|
||||
}
|
||||
|
||||
public function performReadonlyTransformation() {
|
||||
$field = $this->castedCopy('LookupField');
|
||||
$field->setSource($this->getSource());
|
||||
$field->setReadonly(true);
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
public function performDisabledTransformation() {
|
||||
$clone = clone $this;
|
||||
$clone->setDisabled(true);
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns another instance of this field, but "cast" to a different class.
|
||||
*
|
||||
* @see FormField::castedCopy()
|
||||
*
|
||||
* @param String $classOrCopy
|
||||
* @return FormField
|
||||
*/
|
||||
public function castedCopy($classOrCopy) {
|
||||
$field = parent::castedCopy($classOrCopy);
|
||||
if($field instanceof SelectField) {
|
||||
$field->setSource($this->getSource());
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
}
|
121
forms/SingleSelectField.php
Normal file
121
forms/SingleSelectField.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Represents the base class for a single-select field
|
||||
*/
|
||||
abstract class SingleSelectField extends SelectField {
|
||||
|
||||
/**
|
||||
* Show the first <option> element as empty (not having a value),
|
||||
* with an optional label defined through {@link $emptyString}.
|
||||
* By default, the <select> element will be rendered with the
|
||||
* first option from {@link $source} selected.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasEmptyDefault = false;
|
||||
|
||||
/**
|
||||
* The title shown for an empty default selection,
|
||||
* e.g. "Select...".
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $emptyString = '';
|
||||
|
||||
/**
|
||||
* @param boolean $bool
|
||||
* @return self Self reference
|
||||
*/
|
||||
public function setHasEmptyDefault($bool) {
|
||||
$this->hasEmptyDefault = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getHasEmptyDefault() {
|
||||
return $this->hasEmptyDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default selection label, e.g. "select...".
|
||||
* Defaults to an empty string. Automatically sets
|
||||
* {@link $hasEmptyDefault} to true.
|
||||
*
|
||||
* @param string $string
|
||||
*/
|
||||
public function setEmptyString($string) {
|
||||
$this->setHasEmptyDefault(true);
|
||||
$this->emptyString = $string;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmptyString() {
|
||||
return $this->emptyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source array, including the empty string, if present
|
||||
*
|
||||
* @return array|ArrayAccess
|
||||
*/
|
||||
public function getSourceEmpty() {
|
||||
// Inject default option
|
||||
if($this->getHasEmptyDefault()) {
|
||||
return array('' => $this->getEmptyString()) + $this->getSource();
|
||||
} else {
|
||||
return $this->getSource();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this field
|
||||
*
|
||||
* @param Validator $validator
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($validator) {
|
||||
// Check if valid value is given
|
||||
$selected = $this->Value();
|
||||
if(strlen($selected)) {
|
||||
// Use selection rules to check which are valid
|
||||
foreach($this->getValidValues() as $formValue) {
|
||||
if($this->isSelectedValue($formValue, $selected)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($this->getHasEmptyDefault()) {
|
||||
// Check empty value
|
||||
return true;
|
||||
}
|
||||
$selected = '(none)';
|
||||
}
|
||||
|
||||
// Fail
|
||||
$validator->validationError(
|
||||
$this->name,
|
||||
_t(
|
||||
'DropdownField.SOURCE_VALIDATION',
|
||||
"Please select a value within the list provided. {value} is not a valid option",
|
||||
array('value' => $selected)
|
||||
),
|
||||
"validation"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function castedCopy($classOrCopy) {
|
||||
$field = parent::castedCopy($classOrCopy);
|
||||
if($field instanceof SingleSelectField && $this->getHasEmptyDefault()) {
|
||||
$field->setEmptyString($this->getEmptyString());
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
|
||||
}
|
43
model/Relation.php
Normal file
43
model/Relation.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Model;
|
||||
|
||||
use DBField;
|
||||
use SS_Filterable;
|
||||
use SS_Limitable;
|
||||
use SS_List;
|
||||
use SS_Sortable;
|
||||
|
||||
/**
|
||||
* Abstract representation of a DB relation field, either saved or in memory
|
||||
*
|
||||
* @package framework
|
||||
* @subpackage model
|
||||
*/
|
||||
interface Relation extends SS_List, SS_Filterable, SS_Sortable, SS_Limitable {
|
||||
|
||||
/**
|
||||
* Sets the ComponentSet to be the given ID list.
|
||||
* Records will be added and deleted as appropriate.
|
||||
*
|
||||
* @param array $idList List of IDs.
|
||||
*/
|
||||
public function setByIDList($idList);
|
||||
|
||||
/**
|
||||
* Returns an array with both the keys and values set to the IDs of the records in this list.
|
||||
*
|
||||
* Does not return the IDs for unsaved DataObjects
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getIDList();
|
||||
|
||||
/**
|
||||
* Return the DBField object that represents the given field on the related class.
|
||||
*
|
||||
* @param string $fieldName Name of the field
|
||||
* @return DBField The field as a DBField object
|
||||
*/
|
||||
public function dbObject($fieldName);
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Model\Relation;
|
||||
|
||||
/**
|
||||
* A DataList that represents a relation.
|
||||
*
|
||||
@ -8,7 +10,7 @@
|
||||
* @package framework
|
||||
* @subpackage model
|
||||
*/
|
||||
abstract class RelationList extends DataList {
|
||||
abstract class RelationList extends DataList implements Relation {
|
||||
|
||||
public function getForeignID() {
|
||||
return $this->dataQuery->getQueryParam('Foreign.ID');
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Model\Relation;
|
||||
|
||||
/**
|
||||
* An {@link ArrayList} that represents an unsaved relation.
|
||||
*
|
||||
@ -16,7 +18,7 @@
|
||||
* @package framework
|
||||
* @subpackage model
|
||||
*/
|
||||
class UnsavedRelationList extends ArrayList {
|
||||
class UnsavedRelationList extends ArrayList implements Relation {
|
||||
|
||||
/**
|
||||
* The DataObject class name that this relation is on
|
||||
@ -151,21 +153,6 @@ class UnsavedRelationList extends ArrayList {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given column can be used to filter the records.
|
||||
*/
|
||||
public function canFilterBy($by) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the given column can be used to sort the records.
|
||||
*/
|
||||
public function canSortBy($by) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all items from this relation.
|
||||
*/
|
||||
@ -177,7 +164,7 @@ class UnsavedRelationList extends ArrayList {
|
||||
/**
|
||||
* Remove the items from this list with the given IDs
|
||||
*
|
||||
* @param array $idList
|
||||
* @param array $items
|
||||
*/
|
||||
public function removeMany($items) {
|
||||
$this->items = array_diff($this->items, $items);
|
||||
@ -283,156 +270,4 @@ class UnsavedRelationList extends ArrayList {
|
||||
public function dbObject($fieldName) {
|
||||
return singleton($this->dataClass)->dbObject($fieldName);
|
||||
}
|
||||
|
||||
/**#@+
|
||||
* Prevents calling DataList methods that rely on the objects being saved
|
||||
*/
|
||||
public function addFilter() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function alterDataQuery() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function avg() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function byIDs() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function byID($id) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function dataQuery() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function exclude() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function filter() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function getRange($offset, $length) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function innerJoin() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function insertFirst() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function join() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function leftJoin() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function limit($length, $offset = 0) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function map($keyField = 'ID', $titleField = 'Title') {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function max() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function merge($with) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function min() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function newObject() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function offsetExists($offset) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function offsetGet($offset) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function pop() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function relation() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function removeByFilter() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function removeByID() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function reverse() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function setDataModel() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function setDataQuery() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function setQueriedColumns() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function shift() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function sql() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function subtract() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function sum() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function unshift($item) {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
|
||||
public function where() {
|
||||
throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList.");
|
||||
}
|
||||
/**#@-*/
|
||||
}
|
||||
|
@ -202,7 +202,6 @@ class Group extends DataObject {
|
||||
}
|
||||
|
||||
$rolesField = ListboxField::create('Roles', false, $allRoles->map()->toArray())
|
||||
->setMultiple(true)
|
||||
->setDefaultItems($groupRoleIDs)
|
||||
->setAttribute('data-placeholder', _t('Group.AddRole', 'Add a role for this group'))
|
||||
->setDisabledItems($inheritedRoleIDs);
|
||||
|
@ -1342,7 +1342,6 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
asort($groupsMap);
|
||||
$fields->addFieldToTab('Root.Main',
|
||||
ListboxField::create('DirectGroups', singleton('Group')->i18n_plural_name())
|
||||
->setMultiple(true)
|
||||
->setSource($groupsMap)
|
||||
->setAttribute(
|
||||
'data-placeholder',
|
||||
|
12
templates/Includes/GroupedDropdownFieldOption.ss
Normal file
12
templates/Includes/GroupedDropdownFieldOption.ss
Normal file
@ -0,0 +1,12 @@
|
||||
<% if $Options %>
|
||||
<optgroup label="$Title.ATT">
|
||||
<% loop $Options %>
|
||||
<% include GroupedDropdownFieldOption %>
|
||||
<% end_loop %>
|
||||
</optgroup>
|
||||
<% else %>
|
||||
<option value="$Value.ATT"
|
||||
<% if $Selected %> selected="selected"<% end_if %>
|
||||
<% if $Disabled %> disabled="disabled"<% end_if %>
|
||||
><% if $Title %>$Title.XML<% else %> <% end_if %></option>
|
||||
<% end_if %>
|
@ -1,5 +1,8 @@
|
||||
<select $AttributesHTML>
|
||||
<% loop $Options %>
|
||||
<option value="$Value.XML"<% if $Selected %> selected="selected"<% end_if %><% if $Disabled %> disabled="disabled"<% end_if %>><% if Title %>$Title.XML<% else %> <% end_if %></option>
|
||||
<option value="$Value.XML"
|
||||
<% if $Selected %> selected="selected"<% end_if %>
|
||||
<% if $Disabled %> disabled="disabled"<% end_if %>
|
||||
><% if $Title %>$Title.XML<% else %> <% end_if %></option>
|
||||
<% end_loop %>
|
||||
</select>
|
||||
|
5
templates/forms/GroupedDropdownField.ss
Normal file
5
templates/forms/GroupedDropdownField.ss
Normal file
@ -0,0 +1,5 @@
|
||||
<select $AttributesHTML>
|
||||
<% loop $Options %>
|
||||
<% include GroupedDropdownFieldOption %>
|
||||
<% end_loop %>
|
||||
</select>
|
5
templates/forms/ListboxField.ss
Normal file
5
templates/forms/ListboxField.ss
Normal file
@ -0,0 +1,5 @@
|
||||
<select $AttributesHTML>
|
||||
<% loop $Options %>
|
||||
<option value="$Value.XML"<% if $Selected %> selected="selected"<% end_if %><% if $Disabled %> disabled="disabled"<% end_if %>>$Title.XML</option>
|
||||
<% end_loop %>
|
||||
</select>
|
14
templates/forms/MemberDatetimeOptionsetField.ss
Normal file
14
templates/forms/MemberDatetimeOptionsetField.ss
Normal file
@ -0,0 +1,14 @@
|
||||
<ul $AttributesHTML>
|
||||
<% loop $Options %>
|
||||
<li class="$Class">
|
||||
<input id="$ID" class="radio" name="$Name" type="radio" value="$Value.ATT" <% if $isChecked %> checked<% end_if %><% if $isDisabled %> disabled<% end_if %> />
|
||||
<label for="$ID">$Title.XML</label>
|
||||
<% if $CustomName %>
|
||||
<input class="customFormat cms-help cms-help-tooltip" name="$CustomName" value="$CustomValue.ATT">
|
||||
<% if $CustomPreview %>
|
||||
<span class="preview">({$CustomPreviewLabel.XML}: "{$CustomPreview.XML}")</span>
|
||||
<% end_if %>
|
||||
<% end_if %>
|
||||
</li>
|
||||
<% end_loop %>
|
||||
</ul>
|
@ -49,6 +49,39 @@ class CheckboxSetFieldTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Test different data sources
|
||||
*/
|
||||
public function testSources() {
|
||||
// Array
|
||||
$items = array('a' => 'Apple', 'b' => 'Banana', 'c' => 'Cranberry');
|
||||
$field = new CheckboxSetField('Field', null, $items);
|
||||
$this->assertEquals($items, $field->getSource());
|
||||
|
||||
// SS_List
|
||||
$list = new ArrayList(array(
|
||||
new ArrayData(array(
|
||||
'ID' => 'a',
|
||||
'Title' => 'Apple'
|
||||
)),
|
||||
new ArrayData(array(
|
||||
'ID' => 'b',
|
||||
'Title' => 'Banana'
|
||||
)),
|
||||
new ArrayData(array(
|
||||
'ID' => 'c',
|
||||
'Title' => 'Cranberry'
|
||||
))
|
||||
));
|
||||
$field2 = new CheckboxSetField('Field', null, $list);
|
||||
$this->assertEquals($items, $field2->getSource());
|
||||
|
||||
$field3 = new CheckboxSetField('Field', null, $list->map());
|
||||
$this->assertEquals($items, $field3->getSource());
|
||||
}
|
||||
|
||||
public function testSaveWithNothingSelected() {
|
||||
$article = $this->objFromFixture('CheckboxSetFieldTest_Article', 'articlewithouttags');
|
||||
|
||||
@ -73,11 +106,11 @@ class CheckboxSetFieldTest extends SapphireTest {
|
||||
$tag1 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag1');
|
||||
$tag2 = $this->objFromFixture('CheckboxSetFieldTest_Tag', 'tag2');
|
||||
|
||||
/* Create a CheckboxSetField with 2 items selected. Note that the array is in the format (key) => (selected) */
|
||||
/* Create a CheckboxSetField with 2 items selected. Note that the array is a list of values */
|
||||
$field = new CheckboxSetField("Tags", "Test field", DataObject::get("CheckboxSetFieldTest_Tag")->map());
|
||||
$field->setValue(array(
|
||||
$tag1->ID => true,
|
||||
$tag2->ID => true
|
||||
$tag1->ID,
|
||||
$tag2->ID
|
||||
));
|
||||
|
||||
/* Saving should work */
|
||||
@ -115,12 +148,14 @@ class CheckboxSetFieldTest extends SapphireTest {
|
||||
new FieldList()
|
||||
);
|
||||
$form->loadDataFrom($articleWithTags);
|
||||
$value = $field->Value();
|
||||
sort($value);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
$tag1->ID => $tag1->ID,
|
||||
$tag2->ID => $tag2->ID
|
||||
$tag1->ID,
|
||||
$tag2->ID
|
||||
),
|
||||
$field->Value(),
|
||||
$value,
|
||||
'CheckboxSetField loads data from a manymany relationship in an object through Form->loadDataFrom()'
|
||||
);
|
||||
}
|
||||
@ -141,33 +176,36 @@ class CheckboxSetFieldTest extends SapphireTest {
|
||||
$article->ID
|
||||
))->value();
|
||||
|
||||
$this->assertEquals('Test,Another', $dbValue);
|
||||
// JSON encoded values
|
||||
$this->assertEquals('["Test","Another"]', $dbValue);
|
||||
}
|
||||
|
||||
public function testValidationWithArray() {
|
||||
//test with array input
|
||||
// Test with array input
|
||||
$field = CheckboxSetField::create('Test', 'Testing', array(
|
||||
"One" => "One",
|
||||
"Two" => "Two",
|
||||
"Three" => "Three"
|
||||
));
|
||||
$validator = new RequiredFields();
|
||||
$field->setValue(array("One" => "One", "Two" => "Two"));
|
||||
$field->setValue(array("One", "Two"));
|
||||
$this->assertTrue(
|
||||
$field->validate($validator),
|
||||
'Field validates values within source array'
|
||||
);
|
||||
//non valid value should fail
|
||||
|
||||
// Non valid value should fail
|
||||
$field->setValue(array("Four" => "Four"));
|
||||
$this->assertFalse(
|
||||
$field->validate($validator),
|
||||
'Field does not validate values outside of source array'
|
||||
);
|
||||
//non valid value included with valid options should succeed
|
||||
$field->setValue(array("One" => "One", "Two" => "Two", "Four" => "Four"));
|
||||
$this->assertTrue(
|
||||
|
||||
// Non valid value, even if included with valid options, should fail
|
||||
$field->setValue(array("One", "Two", "Four"));
|
||||
$this->assertFalse(
|
||||
$field->validate($validator),
|
||||
'Field validates when presented with mixed valid and invalid values'
|
||||
'Field does not validate when presented with mixed valid and invalid values'
|
||||
);
|
||||
}
|
||||
|
||||
@ -200,9 +238,9 @@ class CheckboxSetFieldTest extends SapphireTest {
|
||||
$tag2->ID => $tag2->ID,
|
||||
$tag3->ID => $tag3->ID
|
||||
));
|
||||
$this->assertTrue(
|
||||
$this->assertFalse(
|
||||
$field->validate($validator),
|
||||
'Validates when presented with mixed valid and invalid values'
|
||||
'Field does not validate when presented with mixed valid and invalid values'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,37 @@ class DropdownFieldTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test different data sources
|
||||
*/
|
||||
public function testSources() {
|
||||
// Array
|
||||
$items = array('a' => 'Apple', 'b' => 'Banana', 'c' => 'Cranberry');
|
||||
$field = new DropdownField('Field', null, $items);
|
||||
$this->assertEquals($items, $field->getSource());
|
||||
|
||||
// SS_List
|
||||
$list = new ArrayList(array(
|
||||
new ArrayData(array(
|
||||
'ID' => 'a',
|
||||
'Title' => 'Apple'
|
||||
)),
|
||||
new ArrayData(array(
|
||||
'ID' => 'b',
|
||||
'Title' => 'Banana'
|
||||
)),
|
||||
new ArrayData(array(
|
||||
'ID' => 'c',
|
||||
'Title' => 'Cranberry'
|
||||
))
|
||||
));
|
||||
$field2 = new DropdownField('Field', null, $list);
|
||||
$this->assertEquals($items, $field2->getSource());
|
||||
|
||||
$field3 = new DropdownField('Field', null, $list->map());
|
||||
$this->assertEquals($items, $field3->getSource());
|
||||
}
|
||||
|
||||
public function testReadonlyField() {
|
||||
$field = new DropdownField('FeelingOk', 'Are you feeling ok?', array(0 => 'No', 1 => 'Yes'));
|
||||
$field->setEmptyString('(Select one)');
|
||||
|
@ -18,6 +18,8 @@ class GroupedDropdownFieldTest extends SapphireTest {
|
||||
)
|
||||
));
|
||||
|
||||
$this->assertEquals(array("1", "2", "3", "4"), $field->getValidValues());
|
||||
|
||||
$validator = new RequiredFields();
|
||||
|
||||
$field->setValue("1");
|
||||
@ -43,11 +45,10 @@ class GroupedDropdownFieldTest extends SapphireTest {
|
||||
//disabled items shouldn't validate
|
||||
$field->setDisabledItems(array('1'));
|
||||
$field->setValue('1');
|
||||
$this->assertFalse($field->validate($validator));
|
||||
|
||||
//grouped disabled items shouldn't validate
|
||||
$field->setDisabledItems(array("Group One" => array("2")));
|
||||
$field->setValue('2');
|
||||
$this->assertEquals(array("2", "3", "4"), $field->getValidValues());
|
||||
$this->assertEquals(array("1"), $field->getDisabledItems());
|
||||
|
||||
$this->assertFalse($field->validate($validator));
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$tag2 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag2');
|
||||
$tag3 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag3');
|
||||
$field = new ListboxField("Tags", "Test field", DataObject::get("ListboxFieldTest_Tag")->map()->toArray());
|
||||
$field->setMultiple(true);
|
||||
$field->setValue(null, $articleWithTags);
|
||||
|
||||
$p = new CSSContentParser($field->Field());
|
||||
@ -35,7 +34,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$tag2 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag2');
|
||||
$tag3 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag3');
|
||||
$field = new ListboxField("Tags", "Test field", DataObject::get("ListboxFieldTest_Tag")->map()->toArray());
|
||||
$field->setMultiple(true);
|
||||
$field->setValue(null, $articleWithTags);
|
||||
$field->setDisabledItems(array($tag1->ID, $tag3->ID));
|
||||
|
||||
@ -54,7 +52,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
public function testSaveIntoNullValueWithMultipleOff() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = true;
|
||||
|
||||
$obj = new ListboxFieldTest_DataObject();
|
||||
$field->setValue('a');
|
||||
@ -67,10 +64,9 @@ class ListboxFieldTest extends SapphireTest {
|
||||
public function testSaveIntoNullValueWithMultipleOn() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = true;
|
||||
|
||||
$obj = new ListboxFieldTest_DataObject();
|
||||
$field->setValue('a,c');
|
||||
$field->setValue(array('a', 'c'));
|
||||
$field->saveInto($obj);
|
||||
$field->setValue('');
|
||||
$field->saveInto($obj);
|
||||
@ -80,30 +76,30 @@ class ListboxFieldTest extends SapphireTest {
|
||||
public function testSaveInto() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = false;
|
||||
|
||||
$obj = new ListboxFieldTest_DataObject();
|
||||
$field->setValue('a');
|
||||
$field->saveInto($obj);
|
||||
$this->assertEquals('a', $obj->Choices);
|
||||
$this->assertEquals('["a"]', $obj->Choices);
|
||||
}
|
||||
|
||||
public function testSaveIntoMultiple() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = true;
|
||||
|
||||
// As array
|
||||
$obj1 = new ListboxFieldTest_DataObject();
|
||||
$field->setValue(array('a', 'c'));
|
||||
$field->saveInto($obj1);
|
||||
$this->assertEquals('a,c', $obj1->Choices);
|
||||
$this->assertEquals('["a","c"]', $obj1->Choices);
|
||||
|
||||
// As string
|
||||
$obj2 = new ListboxFieldTest_DataObject();
|
||||
$field->setValue('a,c');
|
||||
$obj2->Choices = '["a","c"]';
|
||||
$field->setValue(null, $obj2);
|
||||
$this->assertEquals(array('a', 'c'), $field->Value());
|
||||
$field->saveInto($obj2);
|
||||
$this->assertEquals('a,c', $obj2->Choices);
|
||||
$this->assertEquals('["a","c"]', $obj2->Choices);
|
||||
}
|
||||
|
||||
public function testSaveIntoManyManyRelation() {
|
||||
@ -112,7 +108,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$tag1 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag1');
|
||||
$tag2 = $this->objFromFixture('ListboxFieldTest_Tag', 'tag2');
|
||||
$field = new ListboxField("Tags", "Test field", DataObject::get("ListboxFieldTest_Tag")->map()->toArray());
|
||||
$field->setMultiple(true);
|
||||
|
||||
// Save new relations
|
||||
$field->setValue(array($tag1->ID,$tag2->ID));
|
||||
@ -133,37 +128,9 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$this->assertEquals(array(), $article->Tags()->sort('ID')->column('ID'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetValueFailsOnArrayIfMultipleIsOff() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = false;
|
||||
|
||||
// As array (type error)
|
||||
$failsOnArray = false;
|
||||
$obj = new ListboxFieldTest_DataObject();
|
||||
$field->setValue(array('a', 'c'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testSetValueFailsOnStringIfChoiceInvalidAndMultipleIsOff() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = false;
|
||||
|
||||
// As string (invalid choice as comma is regarded literal)
|
||||
$obj = new ListboxFieldTest_DataObject();
|
||||
$field->setValue('invalid');
|
||||
}
|
||||
|
||||
public function testFieldRenderingMultipleOff() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = true;
|
||||
$field->setValue('a');
|
||||
$parser = new CSSContentParser($field->Field());
|
||||
$optEls = $parser->getBySelector('option');
|
||||
@ -176,8 +143,7 @@ class ListboxFieldTest extends SapphireTest {
|
||||
public function testFieldRenderingMultipleOn() {
|
||||
$choices = array('a' => 'a value', 'b' => 'b value','c' => 'c value');
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
$field->multiple = true;
|
||||
$field->setValue('a,c');
|
||||
$field->setValue(array('a', 'c'));
|
||||
$parser = new CSSContentParser($field->Field());
|
||||
$optEls = $parser->getBySelector('option');
|
||||
$this->assertEquals(3, count($optEls));
|
||||
@ -186,14 +152,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$this->assertEquals('selected', (string)$optEls[2]['selected']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException InvalidArgumentException
|
||||
*/
|
||||
public function testCommasInSourceKeys() {
|
||||
$choices = array('a' => 'a value', 'b,with,comma' => 'b value,with,comma',);
|
||||
$field = new ListboxField('Choices', 'Choices', $choices);
|
||||
}
|
||||
|
||||
public function testValidationWithArray() {
|
||||
//test with array input
|
||||
$field = ListboxField::create('Test', 'Testing', array(
|
||||
@ -208,7 +166,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
$field->validate($validator),
|
||||
'Validates values in source map'
|
||||
);
|
||||
$field->setMultiple(true);
|
||||
$field->setValue(array(1));
|
||||
$this->assertTrue(
|
||||
$field->validate($validator),
|
||||
@ -247,7 +204,6 @@ class ListboxFieldTest extends SapphireTest {
|
||||
// $field->validate($validator),
|
||||
// 'Field does not validate values outside of source map'
|
||||
// );
|
||||
$field->setMultiple(true);
|
||||
$field->setValue(false, new ArrayData(array(
|
||||
$tag1->ID => $tag1->ID,
|
||||
$tag2->ID => $tag2->ID
|
||||
|
@ -59,7 +59,7 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest {
|
||||
$field->setForm(new Form(new MemberDatetimeOptionsetFieldTest_Controller(), 'Form', new FieldList(),
|
||||
new FieldList())); // fake form
|
||||
$parser = new CSSContentParser($field->Field());
|
||||
$xmlArr = $parser->getBySelector('#Form_Form_TimeFormat_h_mm_ss_a');
|
||||
$xmlArr = $parser->getBySelector('#Form_Form_TimeFormat_h:mm:ss_a');
|
||||
$this->assertEquals('checked', (string) $xmlArr[0]['checked']);
|
||||
}
|
||||
|
||||
@ -81,8 +81,7 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest {
|
||||
$field->setForm(new Form(new MemberDatetimeOptionsetFieldTest_Controller(), 'Form', new FieldList(),
|
||||
new FieldList())); // fake form
|
||||
$parser = new CSSContentParser($field->Field());
|
||||
$xmlInputArr = $parser->getBySelector('.valCustom input');
|
||||
$xmlPreview = $parser->getBySelector('.preview');
|
||||
$xmlInputArr = $parser->getBySelector('.valcustom input');
|
||||
$this->assertEquals('checked', (string) $xmlInputArr[0]['checked']);
|
||||
$this->assertEquals('dd MM yy', (string) $xmlInputArr[1]['value']);
|
||||
}
|
||||
@ -91,9 +90,15 @@ class MemberDatetimeOptionsetFieldTest extends SapphireTest {
|
||||
$field = new MemberDatetimeOptionsetField('DateFormat', 'DateFormat');
|
||||
$validator = new RequiredFields();
|
||||
$this->assertTrue($field->validate($validator));
|
||||
$_POST['DateFormat_custom'] = 'dd MM yyyy';
|
||||
$field->setValue(array(
|
||||
'Options' => '__custom__',
|
||||
'Custom' => 'dd MM yyyy'
|
||||
));
|
||||
$this->assertTrue($field->validate($validator));
|
||||
$_POST['DateFormat_custom'] = 'sdfdsfdfd1244';
|
||||
$field->setValue(array(
|
||||
'Options' => '__custom__',
|
||||
'Custom' => 'sdfdsfdfd1244'
|
||||
));
|
||||
$this->assertFalse($field->validate($validator));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user