Merge branch 'master' of github.com:silverstripe/sapphire

Conflicts:
	tests/forms/gridfield/GridFieldDetailFormTest.php
	tests/forms/gridfield/GridFieldPopupFormsTest.yml
This commit is contained in:
Sam Minnee 2012-03-09 14:37:32 +13:00
commit a071456837
26 changed files with 203 additions and 1243 deletions

View File

@ -195,11 +195,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
// Audit logging hook
if(empty($_REQUEST['executeForm']) && !$this->isAjax()) $this->extend('accessedCMS');
// Requirements
// Suppress behaviour/prototype validation instructions in CMS, not compatible with ajax loading of forms.
Validator::set_javascript_validation_handler('none');
// Set the members html editor config
HtmlEditorConfig::set_active(Member::currentUser()->getHtmlEditorConfigForCMS());
@ -264,7 +259,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
SAPPHIRE_DIR . '/javascript/DateField.js',
SAPPHIRE_DIR . '/javascript/HtmlEditorField.js',
SAPPHIRE_DIR . '/javascript/TabSet.js',
SAPPHIRE_DIR . '/javascript/Validator.js',
SAPPHIRE_DIR . '/javascript/i18n.js',
SAPPHIRE_ADMIN_DIR . '/javascript/ssui.core.js',
SAPPHIRE_DIR . '/javascript/GridField.js',
@ -913,7 +907,6 @@ class LeftAndMain extends Controller implements PermissionProvider {
// The clientside (mainly LeftAndMain*.js) rely on ajax responses
// which can be evaluated as javascript, hence we need
// to override any global changes to the validation handler.
$validator->setJavascriptValidationHandler('prototype');
$form->setValidator($validator);
} else {
$form->unsetValidator();

View File

@ -392,18 +392,20 @@ class ModelAdmin_CollectionController extends Controller {
* @return Form
*/
public function SearchForm() {
$context = singleton($this->modelClass)->getDefaultSearchContext();
$SNG_model = singleton($this->modelClass);
$context = $SNG_model->getDefaultSearchContext();
$fields = $context->getSearchFields();
$columnSelectionField = $this->ColumnSelectionField();
$fields->push($columnSelectionField);
$validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$validator = ($SNG_model->hasMethod('getCMSValidator')) ? $SNG_model->getCMSValidator() : new RequiredFields();
$clearAction = new ResetFormAction('clearsearch', _t('ModelAdmin.CLEAR_SEARCH','Clear Search'));
$form = new Form($this, "SearchForm",
$fields,
new FieldList(
new FormAction('search', _t('MemberTableField.SEARCH', 'Search')),
$clearAction = new ResetFormAction('clearsearch', _t('ModelAdmin.CLEAR_SEARCH','Clear Search'))
$clearAction
),
$validator
);
@ -423,26 +425,28 @@ class ModelAdmin_CollectionController extends Controller {
*/
public function CreateForm() {
$modelName = $this->modelClass;
$SNG_model = singleton($modelName);
if($this->hasMethod('alternatePermissionCheck')) {
if(!$this->alternatePermissionCheck()) return false;
} else {
if(!singleton($modelName)->canCreate(Member::currentUser())) return false;
if(!$SNG_model->canCreate(Member::currentUser())) return false;
}
$buttonLabel = sprintf(_t('ModelAdmin.CREATEBUTTON', "Create '%s'", PR_MEDIUM, "Create a new instance from a model class"), singleton($modelName)->i18n_singular_name());
$buttonLabel = sprintf(_t('ModelAdmin.CREATEBUTTON', "Create '%s'", PR_MEDIUM, "Create a new instance from a model class"), $SNG_model->i18n_singular_name());
$validator = ($SNG_model->hasMethod('getCMSValidator')) ? $SNG_model->getCMSValidator() : new RequiredFields();
$createButton = FormAction::create('add', $buttonLabel)->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept');
$form = new Form($this, "CreateForm",
new FieldList(),
new FieldList(
$createButton = FormAction::create('add', $buttonLabel)
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
),
$validator = new RequiredFields()
);
new FieldList(),
new FieldList($createButton),
$validator
);
$createButton->dontEscape = true;
$validator->setJavascriptValidationHandler('none');
$form->setHTMLID("Form_CreateForm_" . $this->modelClass);
return $form;
}
@ -498,15 +502,11 @@ class ModelAdmin_CollectionController extends Controller {
new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV'))
);
$validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$form = new Form(
$this,
"ImportForm",
$fields,
$actions,
$validator
$actions
);
$form->setHTMLID("Form_ImportForm_" . $this->modelClass);
return $form;
@ -802,10 +802,8 @@ class ModelAdmin_CollectionController extends Controller {
$fields = $newRecord->getCMSFields();
}
$validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : null;
if(!$validator) $validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : new RequiredFields();
$actions = new FieldList (
FormAction::create("doCreate", _t('ModelAdmin.ADDBUTTON', "Add"))
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
@ -922,7 +920,6 @@ class ModelAdmin_RecordController extends Controller {
}
$validator = ($this->currentRecord->hasMethod('getCMSValidator')) ? $this->currentRecord->getCMSValidator() : new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$actions = $this->currentRecord->getCMSActions();
if($this->currentRecord->canEdit(Member::currentUser())){

View File

@ -73,6 +73,45 @@ not when simply using the CMS or developing other CMS functionality.
If you want to extend the CMS stylesheets for your own projects without SCSS,
please create a new CSS file and link it into the CMS via `[api:LeftAndMain::require_css()]`.
### Built-in Javascript validation removed ###
Built-in client-side form validation using behaviour.js has been removed, and is no longer supported.
Server-side validation remains. Developers are encouraged to use custom Javascript validation on their
forms if requiring client-side validation.
These classes/files have been removed:
Validator.js
CustomRequiredFields.php
These functions are now deprecated and will throw a notice if used:
Validator::set_javascript_validation_handler()
Validator::get_javascript_validator_handler()
Validator::setJavascriptValidationHandler()
These functions have been removed:
Validator::javascript() (abstract function)
Validator::includeJavascriptValidation()
FormField::jsValidation()
AjaxUniqueTextField::jsValidation()
ConfirmedPasswordField::jsValidation()
CreditCardField::jsValidation()
CurrencyField::jsValidation()
CustomRequiredFields::javascript()
DateField::jsValidation()
DatetimeField::jsValidation()
EmailField::jsValidation()
FieldGroup::jsValidation()
FormField::jsValidation()
NumericField::jsValidation()
PhoneNumberField::jsValidation()
RequiredFields::javascript()
TableField::jsValidation()
TimeField::jsValidation()
### FormField consistently adds classes to HTML elements ###
The [api:FormField] API has been refactored to use SilverStripe templates

View File

@ -34,8 +34,6 @@ class AjaxUniqueTextField extends TextField {
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/UniqueFields.js");
$this->jsValidation();
$url = Convert::raw2att( $this->validateURL );
if($this->restrictedRegex)
@ -54,63 +52,7 @@ class AjaxUniqueTextField extends TextField {
return $this->createTag('input', $attributes);
}
function jsValidation() {
$formID = $this->form->FormName();
$id = $this->id();
$url = Director::absoluteBaseURL() . $this->validateURL;
if($this->restrictedRegex) {
$jsCheckFunc = <<<JS
Element.removeClassName(this, 'invalid');
var match = this.value.match(/{$this->restrictedRegex}/);
if(match) {
Element.addClassName(this, 'invalid');
return false;
}
return true;
JS;
} else {
$jsCheckFunc = "return true;";
}
$jsFunc = <<<JS
Behaviour.register({
'#$id' : {
onkeyup: function() {
var self = this;
if(this.checkValid()) {
jQuery.ajax({
'url': '{$url}?ajax=1&{$this->name}=' + encodeURIComponent(this.value),
method: 'get',
success: function(response) {
if(response.responseText == 'ok')
Element.removeClassName(self, 'inuse');
else {
Element.addClassName(self, 'inuse');
}
},
error: function(response) {
}
});
}
},
checkValid: function() {
$jsCheckFunc
}
}
});
JS;
Requirements::customScript($jsFunc, 'func_validateAjaxUniqueTextField');
//return "\$('$formID').validateCurrency('$this->name');";
}
function validate( $validator ) {
$result = DB::query(sprintf(
"SELECT COUNT(*) FROM \"%s\" WHERE \"%s\" = '%s'",
$this->restrictedTable,

View File

@ -203,97 +203,6 @@ class ConfirmedPasswordField extends FormField {
return $this;
}
function jsValidation() {
$formID = $this->form->FormName();
$jsTests = '';
$jsTests .= "
// if fields are hidden, reset values and don't validate
var containers = $$('.showOnClickContainer', $('#'+fieldName));
if(containers.length && !Element.visible(containers[0])) {
passEl.value = null;
confEl.value = null;
return true;
}
";
$error1 = _t('ConfirmedPasswordField.HAVETOMATCH', 'Passwords have to match.');
$jsTests .= "
if(passEl.value != confEl.value) {
validationError(confEl, \"$error1\", \"error\");
return false;
}
";
$error2 = _t('ConfirmedPasswordField.NOEMPTY', 'Passwords can\'t be empty.');
if(!$this->canBeEmpty) {
$jsTests .= "
if(!passEl.value || !confEl.value) {
validationError(confEl, \"$error2\", \"error\");
return false;
}
";
}
if(($this->minLength || $this->maxLength)) {
if($this->minLength && $this->maxLength) {
$limit = "{{$this->minLength},{$this->maxLength}}";
$errorMsg = sprintf(_t('ConfirmedPasswordField.BETWEEN', 'Passwords must be %s to %s characters long.'), $this->minLength, $this->maxLength);
} elseif($this->minLength) {
$limit = "{{$this->minLength}}.*";
$errorMsg = sprintf(_t('ConfirmedPasswordField.ATLEAST', 'Passwords must be at least %s characters long.'), $this->minLength);
} elseif($this->maxLength) {
$limit = "{0,{$this->maxLength}}";
$errorMsg = sprintf(_t('ConfirmedPasswordField.MAXIMUM', 'Passwords must be at most %s characters long.'), $this->maxLength);
}
$limitRegex = '/^.' . $limit . '$/';
$jsTests .= "
if(passEl.value && !passEl.value.match({$limitRegex})) {
validationError(confEl, \"{$errorMsg}\", \"error\");
return false;
}
";
}
$error3 = _t('ConfirmedPasswordField.LEASTONE', 'Passwords must have at least one digit and one alphanumeric character.');
if($this->requireStrongPassword) {
$jsTests .= "
if(!passEl.value.match(/^(([a-zA-Z]+\d+)|(\d+[a-zA-Z]+))[a-zA-Z0-9]*$/)) {
validationError(
confEl,
\"$error3\",
\"error\"
);
return false;
}
";
}
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateConfirmedPassword: function(fieldName) {
var passEl = _CURRENT_FORM.elements['Password[_Password]'];
var confEl = _CURRENT_FORM.elements['Password[_ConfirmPassword]'];
$jsTests
return true;
}
}
});
JS;
Requirements :: customScript($jsFunc, 'func_validateConfirmedPassword');
//return "\$('$formID').validateConfirmedPassword('$this->name');";
return <<<JS
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$this->name')
$('$formID').validateConfirmedPassword('$this->name');
}else{
$('$formID').validateConfirmedPassword('$this->name');
}
JS;
}
/**
* Determines if the field was actually

View File

@ -35,62 +35,6 @@ class CreditCardField extends TextField {
else return $this->value;
}
function jsValidation() {
$formID = $this->form->FormName();
$error1 = _t('CreditCardField.VALIDATIONJS1', 'Please ensure you have entered the');
$error2 = _t('CreditCardField.VALIDATIONJS2', 'credit card number correctly.');
$first = _t('CreditCardField.FIRST', 'first');
$second = _t('CreditCardField.SECOND', 'second');
$third = _t('CreditCardField.THIRD', 'third');
$fourth = _t('CreditCardField.FOURTH', 'fourth');
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateCreditCard: function(fieldName) {
if(!$(fieldName + "_Holder")) return true;
// Creditcards are split into multiple values, so get the inputs from the form.
var cardParts = $(fieldName + "_Holder").getElementsByTagName('input');
var cardisnull = true;
var i=0;
for(i=0; i < cardParts.length ; i++ ){
if(cardParts[i].value == null || cardParts[i].value == "")
cardisnull = cardisnull && true;
else
cardisnull = false;
}
if(!cardisnull){
// Concatenate the string values from the parts of the input.
for(i=0; i < cardParts.length ; i++ ){
// The creditcard number cannot be null, nor have less than 4 digits.
if(
cardParts[i].value == null || cardParts[i].value == "" ||
cardParts[i].value.length < 3 ||
!cardParts[i].value.match(/[0-9]{4}/)
){
switch(i){
case 0: number = "$first"; break;
case 1: number = "$second"; break;
case 2: number = "$third"; break;
case 3: number = "$fourth"; break;
}
validationError(cardParts[i],"$error1 " + number + " $error2","validation",false);
return false;
}
}
}
return true;
}
}
});
JS;
Requirements :: customScript($jsFunc, 'func_validateCreditCard');
return "\$('$formID').validateCreditCard('$this->name');";
}
function validate($validator){
// If the field is empty then don't return an invalidation message
if(!trim(implode("", $this->value))) return true;

View File

@ -47,37 +47,6 @@ class CurrencyField extends TextField {
return new CurrencyField_Readonly($this);
*/
}
/**
* @see http://regexlib.com/REDetails.aspx?regexp_id=126
*/
function jsValidation() {
$formID = $this->form->FormName();
$error = _t('CurrencyField.VALIDATIONJS', 'Please enter a valid currency.');
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateCurrency: function(fieldName) {
var el = _CURRENT_FORM.elements[fieldName];
if(!el || !el.value) return true;
var value = \$F(el);
if(value.length > 0 && !value.match(/^\s*(-?\\\$?|\\\$-?)?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?\s*\$/)) {
validationError(el,"$error","validation",false);
return false;
}
return true;
}
}
});
JS;
Requirements::customScript($jsFunc, 'func_validateCurrency_' .$formID);
return <<<JS
if(\$('$formID')) \$('$formID').validateCurrency('$this->name');
JS;
}
function validate($validator) {
if(!empty ($this->value) && !preg_match('/^\s*(\-?\$?|\$\-?)?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?\s*$/', $this->value)) {

View File

@ -1,148 +0,0 @@
<?php
/**
* CustomRequiredFields allow you to create your own validation on forms, while still having the ability to have required fields (as used in [RequiredFields](http://api.silverstripe.org/current/sapphire/form/RequiredFields.html)).
*
* The constructor of CustomRequiredFields takes an array. Each array element is one of two things - either the name of a field that is required, or an array containing two items, 'js' and 'php'. These items are functions called to validate in javascript or php respectively.
*
* Some useful javascript:
* 1. _CURRENT_FORM is the current form
* 2. _CURRENT_FORM.elements is an array of the fields
* 3. validationError(element, message, type) will create a validation error
* 4. clearErrorMessage(element) will clear the validation error
* 5. require('FieldName') create a required field ($this->requireField('FieldName') is the php equivalent)
*
* An example for creating required fields only if payment type is CreditCard:
*
* <code>
* new CustomRequiredFields(
* array(
* "PaymentMethod",
* array(
* "js" => "
* for( var i = 0; i <= this.elements.PaymentMethod.length -1; i++){
* if(this.elements.PaymentMethod[i].value == 'CC' && this.elements.PaymentMethod[i].checked == true){
* require('CardHolderName');
* require('CreditCardNumber');
* require('DateExpiry');
* }
* }
*
* ",
* "php" => 'if($data[PaymentMethod] == "CC") {
* $this->requireField($field,"$field is required","required");
* $this->requireField("CardHolderName", $data);
* $this->requireField("CreditCardNumber", $data);
* $this->requireField("DateExpiry", $data);
* }',
* )
* )
* );
* </code>
*
* And example for confirming mobile number and email address:
*
* <code>
* $js = <<<JS
* if(_CURRENT_FORM.elements["MobileNumberConfirm"].value == _CURRENT_FORM.elements["MobileNumber"].value) {
* clearErrorMessage(_CURRENT_FORM.elements["MobileNumberConfirm"].parentNode);
* } else {
* validationError(_CURRENT_FORM.elements["MobileNumberConfirm"], "Mobile numbers do not match", "validation");
* }
* JS;
*
* $js2 = <<<JS2
* if(_CURRENT_FORM.elements["EmailConfirm"].value == _CURRENT_FORM.elements["Email"].value) {
* clearErrorMessage(_CURRENT_FORM.elements["EmailConfirm"].parentNode);
* } else {
* validationError(_CURRENT_FORM.elements["EmailConfirm"], "Email addresses do not match", "validation");
* }
* JS2;
*
* //create validator
* $validator=new CustomRequiredFields(array('FirstName', 'Surname', 'Email', 'MobileNumber', array('js' => $js, 'php' => 'return true;'), array('js' => $js2, 'php'=>'return true;')));
* </code>
*
* @package forms
* @subpackage validators
*/
class CustomRequiredFields extends RequiredFields{
protected $required;
/**
* Pass each field to be validated as a seperate argument
* @param $required array The list of required fields
*/
function __construct($required) {
$this->required = $required;
}
/**
* Creates the client side validation from form fields
* which is generated at the header of each page
*/
function javascript() {
$code = '';
$fields = $this->form->Fields();
foreach($fields as $field){
//if the field type has some special specific specification for validation of itself
$valid = $field->jsValidation();
if($valid){
$code .= $valid;
}
}
if(is_array($this->required)){
foreach($this->required as $field) {
if(is_array($field) && isset($field['js'])){
$code .= $field['js'] . "\n";
}else if($fields->dataFieldByName($field)) {
$code .= " require('$field');\n";
//Tabs for output tabbing :-)
}
}
}else{
USER_ERROR("CustomRequiredFields::requiredfields is not set / not an array",E_USER_WARNING);
}
return $code;
}
/**
* Creates the server side validation from form fields
* which is executed on form submission
*/
function php($data) {
$fields = $this->form->Fields();
$valid = true;
foreach($fields as $field) {
$valid = ($field->validate($this) && $valid);
}
if($this->required){
foreach($this->required as $key => $fieldName) {
if(is_string($fieldName)) $formField = $fields->dataFieldByName($fieldName);
if(is_array($fieldName) && isset($fieldName['php'])){
eval($fieldName['php']);
}else if($formField) {
// if an error is found, the form is returned.
if(!strlen($data[$fieldName]) || preg_match('/^\s*$/', $data[$fieldName])) {
$this->validationError(
$fieldName,
sprintf(_t('Form.FIELDISREQUIRED', "%s is required."),
$formField->Title()),
"required"
);
return false;
}
}
}
}
return $valid;
}
/**
* allows you too add more required fields to this object after construction.
*/
function appendRequiredFields($requiredFields){
$this->required = array_merge($this->required,$requiredFields->getRequired());
}
}

View File

@ -251,104 +251,6 @@ class DateField extends TextField {
return $field;
}
function jsValidation() {
// JavaScript validation of locales other than en_NZ are not supported at the moment...
if($this->getLocale() != 'en_NZ') return;
$formID = $this->form->FormName();
if(Validator::get_javascript_validator_handler() == 'none') return true;
if($this->getConfig('dmyfields')) {
$error = _t('DateField.VALIDATIONJS', 'Please enter a valid date format.');
// Remove hardcoded date formats from translated strings
$error = preg_replace('/\(.*\)/', '', $error);
$error .= ' (' . $this->getConfig('dateformat') .')';
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateDMYDate: function(fieldName) {
var day_value = \$F(_CURRENT_FORM.elements[fieldName+'[day]']);
var month_value = \$F(_CURRENT_FORM.elements[fieldName+'[month]']);
var year_value = \$F(_CURRENT_FORM.elements[fieldName+'[year]']);
// TODO NZ specific
var value = day_value + '/' + month_value + '/' + year_value;
if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/([0-9][0-9]){1,2}\$/)) {
validationError(_CURRENT_FORM.elements[fieldName+'[day]'],"$error","validation",false);
return false;
}
return true;
}
}
});
JS;
Requirements :: customScript($jsFunc, 'func_validateDMYDate_'.$formID);
return <<<JS
if(\$('$formID')){
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$this->name')
\$('$formID').validateDMYDate('$this->name');
}else{
\$('$formID').validateDMYDate('$this->name');
}
}
JS;
} else {
$error = _t('DateField.VALIDATIONJS', 'Please enter a valid date format (DD/MM/YYYY).');
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateDate: function(fieldName) {
var el = _CURRENT_FORM.elements[fieldName];
if(el)
var value = \$F(el);
if(Element.hasClassName(el, 'dmydate')) {
// dmy triple field validation
var day_value = \$F(_CURRENT_FORM.elements[fieldName+'[day]']);
var month_value = \$F(_CURRENT_FORM.elements[fieldName+'[month]']);
var year_value = \$F(_CURRENT_FORM.elements[fieldName+'[year]']);
// TODO NZ specific
var value = day_value + '/' + month_value + '/' + year_value;
if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/([0-9][0-9]){1,2}\$/)) {
validationError(_CURRENT_FORM.elements[fieldName+'[day]'],"$error","validation",false);
return false;
}
} else {
// single field validation
if(value && value.length > 0 && !value.match(/^[0-9]{1,2}\/[0-9]{1,2}\/[0-90-9]{2,4}\$/)) {
validationError(el,"$error","validation",false);
return false;
}
}
return true;
}
}
});
JS;
Requirements :: customScript($jsFunc, 'func_validateDate_'.$formID);
return <<<JS
if(\$('$formID')){
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$this->name')
\$('$formID').validateDate('$this->name');
}else{
\$('$formID').validateDate('$this->name');
}
}
JS;
}
}
/**
* Validate an array with expected keys 'day', 'month' and 'year.
@ -538,10 +440,6 @@ class DateField_Disabled extends DateField {
return "date_disabled readonly";
}
function jsValidation() {
return null;
}
function validate($validator) {
return true;
}

View File

@ -269,10 +269,6 @@ class DatetimeField extends FormField {
return ($dateValid && $timeValid);
}
function jsValidation() {
return $this->dateField->jsValidation() . $this->timeField->jsValidation();
}
function performReadonlyTransformation() {
$field = new DatetimeField_Readonly($this->name, $this->title, $this->dataValue());
$field->setForm($this->form);
@ -314,10 +310,6 @@ class DatetimeField_Readonly extends DatetimeField {
return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>";
}
function jsValidation() {
return null;
}
function validate($validator) {
return true;
}

View File

@ -10,40 +10,6 @@ class EmailField extends TextField {
return 'email text';
}
function jsValidation() {
$formID = $this->form->FormName();
$error = _t('EmailField.VALIDATIONJS', 'Please enter an email address.');
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateEmailField: function(fieldName) {
var el = _CURRENT_FORM.elements[fieldName];
if(!el || !el.value) return true;
if(el.value.match(/^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i)) {
return true;
} else {
validationError(el, "$error","validation");
return false;
}
}
}
});
JS;
//fix for the problem with more than one form on a page.
Requirements::customScript($jsFunc, 'func_validateEmailField' .'_' . $formID);
//return "\$('$formID').validateEmailField('$this->name');";
return <<<JS
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$this->name')
$('$formID').validateEmailField('$this->name');
}else{
$('$formID').validateEmailField('$this->name');
}
JS;
}
/**
* Validates for RFC 2822 compliant email adresses.
*

View File

@ -169,21 +169,6 @@ HTML;
}
}
/**
* This allows fields within this fieldgroup to still allow them to get valuated.
*/
function jsValidation(){
$fs = $this->FieldList();
$validationCode = '';
foreach($fs as $subfield) {
if($value = $subfield->jsValidation()) {
$validationCode .= $value;
}
}
return $validationCode;
}
function php($data){
return;
}

View File

@ -127,14 +127,6 @@ class Form extends RequestHandler {
*/
protected $securityToken = null;
/**
* HACK This is a temporary hack to allow multiple calls to includeJavascriptValidation on
* the validator (if one is present).
*
* @var boolean
*/
public $jsValidationIncluded = false;
/**
* @var array $extraClasses List of additional CSS classes for the form tag.
*/
@ -324,23 +316,19 @@ class Form extends RequestHandler {
if(!$this->validate()) {
if(Director::is_ajax()) {
// Special case for legacy Validator.js implementation (assumes eval'ed javascript collected through FormResponse)
if($this->validator->getJavascriptValidationHandler() == 'prototype') {
return FormResponse::respond();
$acceptType = $request->getHeader('Accept');
if(strpos($acceptType, 'application/json') !== FALSE) {
// Send validation errors back as JSON with a flag at the start
$response = new SS_HTTPResponse(Convert::array2json($this->validator->getErrors()));
$response->addHeader('Content-Type', 'application/json');
} else {
$acceptType = $request->getHeader('Accept');
if(strpos($acceptType, 'application/json') !== FALSE) {
// Send validation errors back as JSON with a flag at the start
$response = new SS_HTTPResponse(Convert::array2json($this->validator->getErrors()));
$response->addHeader('Content-Type', 'application/json');
} else {
$this->setupFormErrors();
// Send the newly rendered form tag as HTML
$response = new SS_HTTPResponse($this->forTemplate());
$response->addHeader('Content-Type', 'text/html');
}
return $response;
$this->setupFormErrors();
// Send the newly rendered form tag as HTML
$response = new SS_HTTPResponse($this->forTemplate());
$response->addHeader('Content-Type', 'text/html');
}
return $response;
} else {
if($this->getRedirectToFormOnValidationError()) {
if($pageURL = $request->getHeader('Referer')) {
@ -697,9 +685,6 @@ class Form extends RequestHandler {
// Forms shouldn't be cached, cos their error messages won't be shown
HTTP::set_cache_age(0);
// workaround to include javascript validation
if($this->validator && !$this->jsValidationIncluded) $this->validator->includeJavascriptValidation();
$attrs = $this->getAttributes();
// Remove empty
@ -1259,10 +1244,6 @@ class Form extends RequestHandler {
* the attributes of the form. These fields can be used to send the form to Ajax.
*/
function formHtmlContent() {
// Call FormAttributes to force inclusion of custom client-side validation of fields
// because it won't be included by the template
if($this->validator && !$this->jsValidationIncluded) $this->validator->includeJavascriptValidation();
$this->IncludeFormTag = false;
$content = $this->forTemplate();
$this->IncludeFormTag = true;

View File

@ -663,15 +663,6 @@ class FormField extends RequestHandler {
else return "<$tag$preparedAttributes />";
}
/**
* javascript handler Functions for each field type by default
* formfield doesnt have a validation function
*
* @todo shouldn't this be an abstract method?
*/
function jsValidation() {
}
/**
* Validation Functions for each field type by default
* formfield doesnt have a validation function

View File

@ -18,40 +18,6 @@ class NumericField extends TextField{
return 'numeric text';
}
function jsValidation() {
$formID = $this->form->FormName();
$error = _t('NumericField.VALIDATIONJS', 'is not a number, only numbers can be accepted for this field');
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validateNumericField: function(fieldName) {
el = _CURRENT_FORM.elements[fieldName];
if(!el || !el.value) return true;
if(!isNaN(el.value)) {
return true;
} else {
validationError(el, "'" + el.value + "' $error","validation");
return false;
}
}
}
});
JS;
Requirements::customScript($jsFunc, 'func_validateNumericField');
//return "\$('$formID').validateNumericField('$this->name');";
return <<<JS
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$this->name')
$('$formID').validateNumericField('$this->name');
}else{
$('$formID').validateNumericField('$this->name');
}
JS;
}
/** PHP Validation **/
function validate($validator){
if($this->value && !is_numeric(trim($this->value))){

View File

@ -124,51 +124,6 @@ class PhoneNumberField extends FormField {
$record->$fieldName = $completeNumber;
}
/**
* @todo Very basic validation at the moment
*/
function jsValidation() {
$formID = $this->form->FormName();
$jsFunc =<<<JS
Behaviour.register({
"#$formID": {
validatePhoneNumber: function(fieldName) {
if(!$(fieldName + "_Holder")) return true;
// Phonenumbers are split into multiple values, so get the inputs from the form.
var parts = $(fieldName + "_Holder").getElementsByTagName('input');
var isNull = true;
// we're not validating empty fields (done by requiredfields)
for(i=0; i < parts.length ; i++ ) {
isNull = (parts[i].value == null || parts[i].value == "") ? isNull && true : false;
}
if(!isNull) {
// Concatenate the string values from the parts of the input.
var joinedNumber = "";
for(i=0; i < parts.length; i++) joinedNumber += parts[i].value;
if(!joinedNumber.match(/^[0-9\+\-\(\)\s\#]*\$/)) {
// TODO Find a way to mark multiple error fields
validationError(
fieldName+"-Number",
"Please enter a valid phone number",
"validation",
false
);
}
}
return true;
}
}
});
JS;
Requirements :: customScript($jsFunc, 'func_validatePhoneNumber');
return "\$('$formID').validatePhoneNumber('$this->name');";
}
/**
* @todo Very basic validation at the moment
*/

View File

@ -53,39 +53,6 @@ class RequiredFields extends Validator {
return $result;
}
function javascript() {
$js = "";
$fields = $this->form->Fields();
$dataFields = $this->form->Fields()->dataFields();
if($dataFields) {
foreach($dataFields as $field) {
// if the field type has some special specific specification for validation of itself
$validationFunc = $field->jsValidation();
if($validationFunc) $js .= $validationFunc . "\n";
}
}
$useLabels = $this->useLabels ? 'true' : 'false';
if($this->required) {
foreach($this->required as $field) {
if($fields->dataFieldByName($field)) {
//$js .= "\t\t\t\t\trequire('$field', false, $useLabels);\n";
$js .= <<<JS
if(typeof fromAnOnBlur != 'undefined'){\n
if(fromAnOnBlur.name == '$field')\n
require(fromAnOnBlur);\n
}else{
require('$field');
}
JS;
}
}
}
return $js;
}
/**
* Allows validation of fields via specification of a php function for validation which is executed after
* the form is submitted

View File

@ -484,41 +484,6 @@ class TableField extends TableListField {
return $this;
}
function jsValidation() {
$js = "";
$items = $this->Items();
if($items) foreach($items as $item) {
foreach($item->Fields() as $field) {
//if the field type has some special specific specification for validation of itself
$js .= $field->jsValidation($this->form->class."_".$this->form->Name());
}
}
// TODO Implement custom requiredFields
$items = $this->sourceItems();
if($items && $this->requiredFields && $items->count()) {
foreach ($this->requiredFields as $field) {
foreach($items as $item){
$cellName = $this->getName().'['.$item->ID.']['.$field.']';
$js .= "\n";
if($fields->dataFieldByName($cellName)) {
$js .= <<<JS
if(typeof fromAnOnBlur != 'undefined'){
if(fromAnOnBlur.name == '$cellName')
require(fromAnOnBlur);
}else{
require('$cellName');
}
JS;
}
}
}
}
return $js;
}
function php($data) {
$valid = true;

View File

@ -217,10 +217,6 @@ class TimeField_Readonly extends TimeField {
return "<span class=\"readonly\" id=\"" . $this->id() . "\">$val</span>";
}
function jsValidation() {
return null;
}
function validate($validator) {
return true;
}

View File

@ -24,62 +24,26 @@ abstract class Validator extends Object {
* @var array $errors
*/
protected $errors;
/**
* Static for default value of $this->javascriptValidationHandler.
* Set with Validator::set_javascript_validation_handler();
* @var string
*/
protected static $javascript_validation_handler = "prototype";
/**
* Handler for javascript validation. Can be "prototype" or "none".
* @var string
*/
protected $javascriptValidationHandler = null;
/**
* Call this function to set the javascript validation handler for all valdiation on your site.
* This could be called from _config.php to set site-wide javascript validation, or from Controller::init()
* to affect only the front-end site.
* Use instance method {@link setJavascriptValidationHandler()} to
* only set handler for a specific form instance.
*
* @param $handler A string representing the handler to use: 'prototype' or 'none'.
* @todo Add 'jquery' as a handler option.
* @deprecated 3.0 Use custom javascript validation instead
*/
public static function set_javascript_validation_handler($handler) {
if($handler == 'prototype' || $handler == 'none') {
self::$javascript_validation_handler = $handler;
} else {
user_error("Validator::setJavascriptValidationHandler() passed bad handler '$handler'", E_USER_WARNING);
}
}
/**
* Returns global validation handler used for all forms by default,
* unless overwritten by {@link setJavascriptValidationHandler()}.
*
* @return string
*/
public static function get_javascript_validator_handler() {
return self::$javascript_validation_handler;
Deprecation::notice('3.0', 'Use custom javascript validation instead.');
}
/**
* Set JavaScript validation for this validator.
* Use static method {@link set_javascript_validation_handler()}
* to set handlers globally.
*
* @param string $handler
* @deprecated 3.0 Use custom javascript validation instead
*/
public static function get_javascript_validator_handler() {
Deprecation::notice('3.0', 'Use custom javascript validation instead.');
}
/**
* @deprecated 3.0 Use custom javascript validation instead
*/
public function setJavascriptValidationHandler($handler) {
if($handler == 'prototype' || $handler == 'none') {
$this->javascriptValidationHandler = $handler;
} else {
user_error("Validator::setJavascriptValidationHandler() passed bad handler '$handler'", E_USER_WARNING);
}
return $this;
Deprecation::notice('3.0', 'Use custom javascript validation instead.');
}
/**
@ -89,9 +53,9 @@ abstract class Validator extends Object {
* @return string
*/
public function getJavascriptValidationHandler() {
return ($this->javascriptValidationHandler) ? $this->javascriptValidationHandler : self::$javascript_validation_handler;
Deprecation::notice('3.0', 'Use custom javascript validation instead.');
}
/**
* @param Form $form
*/
@ -106,7 +70,6 @@ abstract class Validator extends Object {
function validate(){
$this->errors = null;
$this->php($this->form->getData());
return $this->errors;
}
@ -149,78 +112,6 @@ abstract class Validator extends Object {
}else if(!strlen($data[$fieldName])) $this->validationError($fieldName, "$fieldName is required.", "required");
}
function includeJavascriptValidation() {
if($this->getJavascriptValidationHandler() == 'prototype') {
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/prototype/prototype.js");
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/behaviour/behaviour.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/Validator.js");
$code = $this->javascript();
$formID = $this->form->FormName();
$js = <<<JS
Behaviour.register({
'#$formID': {
validate : function(fromAnOnBlur) {
initialiseForm(this, fromAnOnBlur);
$code
var error = hasHadFormError();
if(!error && fromAnOnBlur) clearErrorMessage(fromAnOnBlur);
if(error && !fromAnOnBlur) focusOnFirstErroredField();
return !error;
},
onsubmit : function() {
if(typeof this.bypassValidation == 'undefined' || !this.bypassValidation) return this.validate();
}
},
'#$formID input' : {
initialise: function() {
if(!this.old_onblur) this.old_onblur = function() { return true; }
if(!this.old_onfocus) this.old_onfocus = function() { return true; }
},
onblur : function() {
if(this.old_onblur()) {
// Don't perform instant validation for CalendarDateField fields; it creates usability wierdness.
if(this.parentNode.className.indexOf('calendardate') == -1 || this.value) {
return $('$formID').validate(this);
} else {
return true;
}
}
}
},
'#$formID textarea' : {
initialise: function() {
if(!this.old_onblur) this.old_onblur = function() { return true; }
if(!this.old_onfocus) this.old_onfocus = function() { return true; }
},
onblur : function() {
if(this.old_onblur()) {
return $('$formID').validate(this);
}
}
},
'#$formID select' : {
initialise: function() {
if(!this.old_onblur) this.old_onblur = function() { return true; }
},
onblur : function() {
if(this.old_onblur()) {
return $('$formID').validate(this);
}
}
}
});
JS;
Requirements::customScript($js);
// HACK Notify the form that the validators client-side validation code has already been included
if($this->form) $this->form->jsValidationIncluded = true;
}
}
/**
* Returns true if the named field is "required".
* Used by FormField to return a value for FormField::Required(), to do things like show *s on the form template.
@ -230,8 +121,6 @@ JS;
return false;
}
abstract function javascript();
abstract function php($data);
}

View File

@ -237,7 +237,7 @@ class GridFieldAddExistingAutocompleter implements GridField_HTMLProvider, GridF
return $this->placeholderText;
} else {
$labels = array();
foreach($searchFields as $searchField) {
if($searchFields) foreach($searchFields as $searchField) {
$label = singleton($dataClass)->fieldLabel($searchField);
if($label) $labels[] = $label;
}

View File

@ -1,316 +0,0 @@
// Shortcut-function (until we update to Prototye v1.5)
if(typeof $$ != "Function") $$ = document.getElementsBySelector;
var _CURRENT_FORM;
var _FIRST_ERRORED_FIELD = null;
var _VALIDATIONS_REF = new Array();
function initialiseForm(form, fromAnOnBlur) {
_CURRENT_FORM = form;
_FIRST_ERRORED_FIELD = null;
if(fromAnOnBlur) {
limitValidationErrorsTo(fromAnOnBlur);
} else {
clearValidationErrorLimit();
}
_HAS_HAD_FORM_ERROR = false;
clearValidationErrorCache();
}
function hasHadFormError() {
return _HAS_HAD_FORM_ERROR || !_ERROR_CACHE;
}
function focusOnFirstErroredField() {
try {
_FIRST_ERRORED_FIELD.focus();
} catch(er) {
}
}
/**
* Returns group with the correct classname
*/
function findIndexOf(group,index) {
var i;
for(i = 0; i < group.length; i++) {
if(group[i].className.indexOf(index) > -1) {
return group[i];
}
}
return null;
}
function clearErrorMessage(holderDiv){
//merged by nlou 23/08/2007, r#40674
if(holderDiv.tagName == 'TD'){//for tablefield.
$$('span.message', holderDiv).each(function(el){
Element.hide(el);
}
);
}else{
$$('span.message', holderDiv.parentNode).each(function(el) {
Element.hide(el);
});
}
$$('div.validationError', holderDiv.parentNode).each(function(el) {
Element.removeClassName(el,'validationError');
});
}
function clearAllErrorMessages() {
$$('span.message').each(function(el) {
Element.hide(el);
});
$$('div.validationError').each(function(el) {
Element.removeClassName(el,'validationError');
});
}
function require(fieldName,cachedError) {
el = _CURRENT_FORM.elements[fieldName];
// see if the field is an optionset
if(el == null) {
var descendants = _CURRENT_FORM.getElementsByTagName('*');
el = document.getElementById(fieldName);
if(el == null)
return true;
if(Element.hasClassName(el, 'optionset')) {
el.type = 'optionset';
var options = el.getElementsByTagName('input');
for(var i = 0; i < options.length; i++) {
if(options[i].checked)
if(el.value != null)
el.value += ',' + options[i].value;
else
el.value = options[i].value;
}
}
}
if(el != null) {
// Sets up radio and checkbox validation
if(el.type == 'checkbox' || el.type == 'radio') {
var set = el.checked;
}//merged by nlou 23/08/2007, r#40674
else if(el.type == 'select-one'){
if(el.value == ''||el.value == '0'){
var set = '';
}else{
var set = el.value;
}
}else{
var set = el.value;
}
var baseEl;
var fieldHolder = el;
// Sometimes require events are triggered of
// associative elements like labels ;-p
if(el.type) {
if(el.parentNode.className.indexOf('form') != -1) set = true;
baseEl = el;
} else {
if(_CURRENT_FORM.elements[fieldName]) {
//Some elements are nested and need to be "got"
var i, hasValue = false;
if(_CURRENT_FORM.elements[fieldName].length > 1) {
for(i=0; i < el.length; i++) {
if(el[i].checked && el[i].value) {
hasValue = true;
break;
}
}
if(hasValue) set = true;
else set = "";
baseEl = el[0].parentNode.parentNode;
} else {
set = "";
baseEl = el.parentNode;
}
} else {
set = true;
}
}
// This checks to see if the input has a value, and the field is not a readonly.
if( ( typeof set == 'undefined' || (typeof(set) == 'string' && set.match(/^\s*$/)) ) ) {
//fieldgroup validation
var fieldLabel = findParentLabel(baseEl);
// Some fields do-not have labels, in
// which case we need a blank one
if(fieldLabel == null || fieldLabel == "") {
fieldlabel = "this field";
}
var errorMessage = ss.i18n.sprintf(ss.i18n._t('VALIDATOR.FIELDREQUIRED', 'Please fill out "%s", it is required.'), fieldLabel);
if(baseEl.requiredErrorMsg) errorMessage = baseEl.requiredErrorMsg;
else if(_CURRENT_FORM.requiredErrorMsg) errorMessage = _CURRENT_FORM.requiredErrorMsg;
validationError(baseEl, errorMessage.replace('$FieldLabel', fieldLabel),"required",cachedError);
return false;
} else {
if(!hasHadFormError()) {
if(baseEl) fieldHolder = baseEl.parentNode;
clearErrorMessage(fieldHolder);
}
return true;
}
}
return true;
}
/**
* Returns the label of the blockset which contains the classname left
*/
function findParentLabel(el) {
// If the el's type is HTML then were at the uppermost parent, so return
// null. its handled by the validator function anyway :-)
if(el) {
if(el.className == "undefined") {
return null;
} else {
if(el.className) {
if(el.className.indexOf('field') == 0) {
labels = el.getElementsByTagName('label');
if(labels){
var left = findIndexOf(labels,'left');
var right = findIndexOf(labels,'right');
if(left) {
return strip_tags(left.innerHTML);
} else if(right) {
return strip_tags(right.innerHTML);
} else {
return findParentLabel(el.parentNode);
}
}
}//merged by nlou 23/08/2007, r#40674
else if(el.className.indexOf('tablecolumn') != -1){
return el.className.substring(0, el.className.indexOf('tablecolumn')-1);
}else{
return findParentLabel(el.parentNode);
}
} else {
// Try to find a label with a for value of this field.
if(el.id) {
var labels = $$('label[for=' + el.id + ']');
if(labels && labels.length > 0) return labels[0].innerHTML;
}
return findParentLabel(el.parentNode);
}
}
}
// backup
return "this";
}
/**
* Adds a validation error to an element
*/
function validationError(field,message, messageClass, cacheError) {
if(typeof(field) == 'string') {
field = document.getElementById(field);
}
if(cacheError) {
_ERROR_CACHE[_ERROR_CACHE.length] = {
"field": field,
"message": message,
"messageClass": messageClass
}
return;
}
// The validation function should only be called if you've just left a field,
// or the field is being validated on final submission
if(_LIMIT_VALIDATION_ERRORS && _LIMIT_VALIDATION_ERRORS != field) {
// clearErrorMessage(field.parentNode);
return;
}
_HAS_HAD_FORM_ERROR = true;
// See if the tag has a reference to the validationMessage (quicker than the one below)
var validationMessage = (typeof(_VALIDATIONS_REF[field.id]) != 'undefined')? _VALIDATIONS_REF[field.id] : null;
// Cycle through the elements to see if it has a span
// (for a validation or required messages)
if(!validationMessage) {
//Get the parent holder of the element
var FieldHolder = field.parentNode;
var allSpans = FieldHolder.getElementsByTagName('span');
validationMessage = findIndexOf(allSpans,'message');
}
// If we didn't find it, create it
if(!validationMessage) {
validationMessage = document.createElement('span');
FieldHolder.appendChild(validationMessage);
}
// Keep a reference to it
_VALIDATIONS_REF[field.id] = validationMessage;
// Keep a reference to the first errored field
if(field && !_FIRST_ERRORED_FIELD) _FIRST_ERRORED_FIELD = field;
// Set the attributes
validationMessage.className = "message " + messageClass;
validationMessage.innerHTML = message;
validationMessage.style.display = "block";
// Set Classname on holder
var holder = document.getParentOfElement(field,'div','field');
Element.addClassName(holder, 'validationError');
}
/**
* Set a limitation so that only validation errors for the given element will actually be shown
*/
var _LIMIT_VALIDATION_ERRORS = null;
function limitValidationErrorsTo(field) {
_LIMIT_VALIDATION_ERRORS = field;
}
function clearValidationErrorLimit() {
_LIMIT_VALIDATION_ERRORS = null;
}
function clearValidationErrorCache() {
_ERROR_CACHE = new Array();
}
function showCachedValidationErrors() {
for(i = 0; i < _ERROR_CACHE.length; i++) {
validationError(_ERROR_CACHE[i]["field"],
_ERROR_CACHE[i]["message"],
_ERROR_CACHE[i]["messageClass"],
false);
}
}
function strip_tags(text) {
return text.replace(/<[^>]+>/g,'');
}

View File

@ -1772,27 +1772,4 @@ class Member_Validator extends RequiredFields {
return $valid;
}
/**
* Check if the submitted member data is valid (client-side)
*
* @param array $data Submitted data
* @return bool Returns TRUE if the submitted data is valid, otherwise
* FALSE.
*/
function javascript() {
$js = parent::javascript();
// Execute the validators on the extensions
if($this->extension_instances) {
foreach($this->extension_instances as $extension) {
if(method_exists($extension, 'hasMethod') && $extension->hasMethod('updateJavascript')) {
$extension->updateJavascript($js, $this->form);
}
}
}
return $js;
}
}
}

View File

@ -90,16 +90,13 @@ class MemberLoginForm extends LoginForm {
parent::__construct($controller, $name, $fields, $actions);
// Focus on the email input when the page is loaded
// Only include this if other form JS validation is enabled
if($this->getValidator()->getJavascriptValidationHandler() != 'none') {
Requirements::customScript(<<<JS
(function() {
var el = document.getElementById("MemberLoginForm_LoginForm_Email");
if(el && el.focus) el.focus();
})();
Requirements::customScript(<<<JS
(function() {
var el = document.getElementById("MemberLoginForm_LoginForm_Email");
if(el && el.focus) el.focus();
})();
JS
);
}
);
}
/**

View File

@ -5,7 +5,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
protected $extraDataObjects = array(
'GridFieldDetailFormTest_Person',
'GridFieldDetailFormTest_PeopleGroup'
'GridFieldDetailFormTest_PeopleGroup',
'GridFieldDetailFormTest_Category',
);
@ -82,6 +83,47 @@ class GridFieldDetailFormTest extends FunctionalTest {
$firstperson = $group->People()->First();
$this->assertEquals($firstperson->Surname, 'Baggins');
}
function testNestedEditForm() {
$this->logInWithPermission('ADMIN');
$group = $this->objFromFixture('GridFieldDetailFormTest_PeopleGroup', 'group');
$person = $group->People()->First();
$category = $person->Categories()->First();
// Get first form (GridField managing PeopleGroup)
$response = $this->get('GridFieldDetailFormTest_GroupController');
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$groupEditLink = $parser->getByXpath('//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $group->ID . '")]//a');
$this->assertEquals(
'GridFieldDetailFormTest_GroupController/Form/field/testfield/item/1/edit',
(string)$groupEditLink[0]['href']
);
// Get second level form (GridField managing Person)
$response = $this->get((string)$groupEditLink[0]['href']);
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$personEditLink = $parser->getByXpath('//fieldset[@id="Form_ItemEditForm_People"]//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $person->ID . '")]//a');
$this->assertEquals(
'GridFieldDetailFormTest_GroupController/Form/field/testfield/item/1/ItemEditForm/field/People/item/1/edit',
(string)$personEditLink[0]['href']
);
// Get third level form (GridField managing Category)
$response = $this->get((string)$personEditLink[0]['href']);
$this->assertFalse($response->isError());
$parser = new CSSContentParser($response->getBody());
$categoryEditLink = $parser->getByXpath('//fieldset[@id="Form_ItemEditForm_Categories"]//tr[contains(@class, "ss-gridfield-item") and contains(@data-id, "' . $category->ID . '")]//a');
// Get fourth level form (Category detail view)
$this->assertEquals(
'GridFieldDetailFormTest_GroupController/Form/field/testfield/item/1/ItemEditForm/field/People/item/1/ItemEditForm/field/Categories/item/1/edit',
(string)$categoryEditLink[0]['href']
);
}
}
class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
@ -93,6 +135,22 @@ class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
static $has_one = array(
'Group' => 'GridFieldDetailFormTest_PeopleGroup'
);
static $many_many = array(
'Categories' => 'GridFieldDetailFormTest_Category'
);
function getCMSFields() {
$fields = parent::getCMSFields();
// TODO No longer necessary once FormScaffolder uses GridField
$fields->replaceField('Categories',
Object::create('GridField', 'Categories', 'Categories',
$this->Categories(),
GridFieldConfig_RelationEditor::create()
)
);
return $fields;
}
}
class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly {
@ -103,6 +161,40 @@ class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly
static $has_many = array(
'People' => 'GridFieldDetailFormTest_Person'
);
function getCMSFields() {
$fields = parent::getCMSFields();
// TODO No longer necessary once FormScaffolder uses GridField
$fields->replaceField('People',
Object::create('GridField', 'People', 'People',
$this->People(),
GridFieldConfig_RelationEditor::create()
)
);
return $fields;
}
}
class GridFieldDetailFormTest_Category extends DataObject implements TestOnly {
static $db = array(
'Name' => 'Varchar'
);
static $belongs_many_many = array(
'People' => 'GridFieldDetailFormTest_Person'
);
function getCMSFields() {
$fields = parent::getCMSFields();
// TODO No longer necessary once FormScaffolder uses GridField
$fields->replaceField('People',
Object::create('GridField', 'People', 'People',
$this->People(),
GridFieldConfig_RelationEditor::create()
)
);
return $fields;
}
}
class GridFieldDetailFormTest_Controller extends Controller implements TestOnly {
@ -120,4 +212,13 @@ class GridFieldDetailFormTest_Controller extends Controller implements TestOnly
}
}
?>
class GridFieldDetailFormTest_GroupController extends Controller implements TestOnly {
protected $template = 'BlankPage';
function Form() {
$field = new GridField('testfield', 'testfield', DataList::create('GridFieldDetailFormTest_PeopleGroup'));
$field->getConfig()->addComponent($gridFieldForm = new GridFieldPopupForms($this, 'Form'));
$field->getConfig()->addComponent(new GridFieldEditAction());
return new Form($this, 'Form', new FieldList($field), new FieldList());
}
}

View File

@ -10,3 +10,8 @@ GridFieldDetailFormTest_PeopleGroup:
group:
Name: My Group
People: =>GridFieldDetailFormTest_Person.joe,=>GridFieldDetailFormTest_Person.jane
GridFieldDetailFormTest_Category:
category1:
Name: Category 1
People: =>GridFieldDetailFormTest_Person.joe,=>GridFieldDetailFormTest_Person.jane