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

View File

@ -392,18 +392,20 @@ class ModelAdmin_CollectionController extends Controller {
* @return Form * @return Form
*/ */
public function SearchForm() { public function SearchForm() {
$context = singleton($this->modelClass)->getDefaultSearchContext(); $SNG_model = singleton($this->modelClass);
$context = $SNG_model->getDefaultSearchContext();
$fields = $context->getSearchFields(); $fields = $context->getSearchFields();
$columnSelectionField = $this->ColumnSelectionField(); $columnSelectionField = $this->ColumnSelectionField();
$fields->push($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", $form = new Form($this, "SearchForm",
$fields, $fields,
new FieldList( new FieldList(
new FormAction('search', _t('MemberTableField.SEARCH', 'Search')), new FormAction('search', _t('MemberTableField.SEARCH', 'Search')),
$clearAction = new ResetFormAction('clearsearch', _t('ModelAdmin.CLEAR_SEARCH','Clear Search')) $clearAction
), ),
$validator $validator
); );
@ -423,26 +425,28 @@ class ModelAdmin_CollectionController extends Controller {
*/ */
public function CreateForm() { public function CreateForm() {
$modelName = $this->modelClass; $modelName = $this->modelClass;
$SNG_model = singleton($modelName);
if($this->hasMethod('alternatePermissionCheck')) { if($this->hasMethod('alternatePermissionCheck')) {
if(!$this->alternatePermissionCheck()) return false; if(!$this->alternatePermissionCheck()) return false;
} else { } 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", $form = new Form($this, "CreateForm",
new FieldList(), new FieldList(),
new FieldList( new FieldList($createButton),
$createButton = FormAction::create('add', $buttonLabel) $validator
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept') );
),
$validator = new RequiredFields()
);
$createButton->dontEscape = true; $createButton->dontEscape = true;
$validator->setJavascriptValidationHandler('none');
$form->setHTMLID("Form_CreateForm_" . $this->modelClass); $form->setHTMLID("Form_CreateForm_" . $this->modelClass);
return $form; return $form;
} }
@ -498,15 +502,11 @@ class ModelAdmin_CollectionController extends Controller {
new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV')) new FormAction('import', _t('ModelAdmin.IMPORT', 'Import from CSV'))
); );
$validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$form = new Form( $form = new Form(
$this, $this,
"ImportForm", "ImportForm",
$fields, $fields,
$actions, $actions
$validator
); );
$form->setHTMLID("Form_ImportForm_" . $this->modelClass); $form->setHTMLID("Form_ImportForm_" . $this->modelClass);
return $form; return $form;
@ -802,9 +802,7 @@ class ModelAdmin_CollectionController extends Controller {
$fields = $newRecord->getCMSFields(); $fields = $newRecord->getCMSFields();
} }
$validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : null; $validator = ($newRecord->hasMethod('getCMSValidator')) ? $newRecord->getCMSValidator() : new RequiredFields();
if(!$validator) $validator = new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$actions = new FieldList ( $actions = new FieldList (
FormAction::create("doCreate", _t('ModelAdmin.ADDBUTTON', "Add")) FormAction::create("doCreate", _t('ModelAdmin.ADDBUTTON', "Add"))
@ -922,7 +920,6 @@ class ModelAdmin_RecordController extends Controller {
} }
$validator = ($this->currentRecord->hasMethod('getCMSValidator')) ? $this->currentRecord->getCMSValidator() : new RequiredFields(); $validator = ($this->currentRecord->hasMethod('getCMSValidator')) ? $this->currentRecord->getCMSValidator() : new RequiredFields();
$validator->setJavascriptValidationHandler('none');
$actions = $this->currentRecord->getCMSActions(); $actions = $this->currentRecord->getCMSActions();
if($this->currentRecord->canEdit(Member::currentUser())){ 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, 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()]`. 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 ### ### FormField consistently adds classes to HTML elements ###
The [api:FormField] API has been refactored to use SilverStripe templates 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::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/UniqueFields.js"); Requirements::javascript(SAPPHIRE_DIR . "/javascript/UniqueFields.js");
$this->jsValidation();
$url = Convert::raw2att( $this->validateURL ); $url = Convert::raw2att( $this->validateURL );
if($this->restrictedRegex) if($this->restrictedRegex)
@ -54,63 +52,7 @@ class AjaxUniqueTextField extends TextField {
return $this->createTag('input', $attributes); 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 ) { function validate( $validator ) {
$result = DB::query(sprintf( $result = DB::query(sprintf(
"SELECT COUNT(*) FROM \"%s\" WHERE \"%s\" = '%s'", "SELECT COUNT(*) FROM \"%s\" WHERE \"%s\" = '%s'",
$this->restrictedTable, $this->restrictedTable,

View File

@ -204,97 +204,6 @@ class ConfirmedPasswordField extends FormField {
return $this; 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 * Determines if the field was actually
* shown on the clientside - if not, * shown on the clientside - if not,

View File

@ -35,62 +35,6 @@ class CreditCardField extends TextField {
else return $this->value; 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){ function validate($validator){
// If the field is empty then don't return an invalidation message // If the field is empty then don't return an invalidation message
if(!trim(implode("", $this->value))) return true; if(!trim(implode("", $this->value))) return true;

View File

@ -48,37 +48,6 @@ class CurrencyField extends TextField {
*/ */
} }
/**
* @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) { function validate($validator) {
if(!empty ($this->value) && !preg_match('/^\s*(\-?\$?|\$\-?)?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?\s*$/', $this->value)) { if(!empty ($this->value) && !preg_match('/^\s*(\-?\$?|\$\-?)?(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?\s*$/', $this->value)) {
$validator->validationError($this->name, _t('Form.VALIDCURRENCY', "Please enter a valid currency."), "validation", false); $validator->validationError($this->name, _t('Form.VALIDCURRENCY', "Please enter a valid currency."), "validation", false);

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

@ -252,104 +252,6 @@ class DateField extends TextField {
return $field; 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. * Validate an array with expected keys 'day', 'month' and 'year.
* Used because Zend_Date::isDate() doesn't provide this. * Used because Zend_Date::isDate() doesn't provide this.
@ -538,10 +440,6 @@ class DateField_Disabled extends DateField {
return "date_disabled readonly"; return "date_disabled readonly";
} }
function jsValidation() {
return null;
}
function validate($validator) { function validate($validator) {
return true; return true;
} }

View File

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

View File

@ -10,40 +10,6 @@ class EmailField extends TextField {
return 'email text'; 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. * 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){ function php($data){
return; return;
} }

View File

@ -127,14 +127,6 @@ class Form extends RequestHandler {
*/ */
protected $securityToken = null; 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. * @var array $extraClasses List of additional CSS classes for the form tag.
*/ */
@ -324,23 +316,19 @@ class Form extends RequestHandler {
if(!$this->validate()) { if(!$this->validate()) {
if(Director::is_ajax()) { if(Director::is_ajax()) {
// Special case for legacy Validator.js implementation (assumes eval'ed javascript collected through FormResponse) // Special case for legacy Validator.js implementation (assumes eval'ed javascript collected through FormResponse)
if($this->validator->getJavascriptValidationHandler() == 'prototype') { $acceptType = $request->getHeader('Accept');
return FormResponse::respond(); 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 { } else {
$acceptType = $request->getHeader('Accept'); $this->setupFormErrors();
if(strpos($acceptType, 'application/json') !== FALSE) { // Send the newly rendered form tag as HTML
// Send validation errors back as JSON with a flag at the start $response = new SS_HTTPResponse($this->forTemplate());
$response = new SS_HTTPResponse(Convert::array2json($this->validator->getErrors())); $response->addHeader('Content-Type', 'text/html');
$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;
} }
return $response;
} else { } else {
if($this->getRedirectToFormOnValidationError()) { if($this->getRedirectToFormOnValidationError()) {
if($pageURL = $request->getHeader('Referer')) { 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 // Forms shouldn't be cached, cos their error messages won't be shown
HTTP::set_cache_age(0); HTTP::set_cache_age(0);
// workaround to include javascript validation
if($this->validator && !$this->jsValidationIncluded) $this->validator->includeJavascriptValidation();
$attrs = $this->getAttributes(); $attrs = $this->getAttributes();
// Remove empty // 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. * the attributes of the form. These fields can be used to send the form to Ajax.
*/ */
function formHtmlContent() { 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; $this->IncludeFormTag = false;
$content = $this->forTemplate(); $content = $this->forTemplate();
$this->IncludeFormTag = true; $this->IncludeFormTag = true;

View File

@ -663,15 +663,6 @@ class FormField extends RequestHandler {
else return "<$tag$preparedAttributes />"; 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 * Validation Functions for each field type by default
* formfield doesnt have a validation function * formfield doesnt have a validation function

View File

@ -18,40 +18,6 @@ class NumericField extends TextField{
return 'numeric text'; 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 **/ /** PHP Validation **/
function validate($validator){ function validate($validator){
if($this->value && !is_numeric(trim($this->value))){ if($this->value && !is_numeric(trim($this->value))){

View File

@ -124,51 +124,6 @@ class PhoneNumberField extends FormField {
$record->$fieldName = $completeNumber; $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 * @todo Very basic validation at the moment
*/ */

View File

@ -53,39 +53,6 @@ class RequiredFields extends Validator {
return $result; 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 * Allows validation of fields via specification of a php function for validation which is executed after
* the form is submitted * the form is submitted

View File

@ -484,41 +484,6 @@ class TableField extends TableListField {
return $this; 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) { function php($data) {
$valid = true; $valid = true;

View File

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

View File

@ -26,60 +26,24 @@ abstract class Validator extends Object {
protected $errors; protected $errors;
/** /**
* Static for default value of $this->javascriptValidationHandler. * @deprecated 3.0 Use custom javascript validation instead
* 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.
*/ */
public static function set_javascript_validation_handler($handler) { public static function set_javascript_validation_handler($handler) {
if($handler == 'prototype' || $handler == 'none') { Deprecation::notice('3.0', 'Use custom javascript validation instead.');
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, * @deprecated 3.0 Use custom javascript validation instead
* unless overwritten by {@link setJavascriptValidationHandler()}.
*
* @return string
*/ */
public static function get_javascript_validator_handler() { 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. * @deprecated 3.0 Use custom javascript validation instead
* Use static method {@link set_javascript_validation_handler()}
* to set handlers globally.
*
* @param string $handler
*/ */
public function setJavascriptValidationHandler($handler) { public function setJavascriptValidationHandler($handler) {
if($handler == 'prototype' || $handler == 'none') { Deprecation::notice('3.0', 'Use custom javascript validation instead.');
$this->javascriptValidationHandler = $handler;
} else {
user_error("Validator::setJavascriptValidationHandler() passed bad handler '$handler'", E_USER_WARNING);
}
return $this;
} }
/** /**
@ -89,7 +53,7 @@ abstract class Validator extends Object {
* @return string * @return string
*/ */
public function getJavascriptValidationHandler() { public function getJavascriptValidationHandler() {
return ($this->javascriptValidationHandler) ? $this->javascriptValidationHandler : self::$javascript_validation_handler; Deprecation::notice('3.0', 'Use custom javascript validation instead.');
} }
/** /**
@ -106,7 +70,6 @@ abstract class Validator extends Object {
function validate(){ function validate(){
$this->errors = null; $this->errors = null;
$this->php($this->form->getData()); $this->php($this->form->getData());
return $this->errors; return $this->errors;
} }
@ -149,78 +112,6 @@ abstract class Validator extends Object {
}else if(!strlen($data[$fieldName])) $this->validationError($fieldName, "$fieldName is required.", "required"); }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". * 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. * 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; return false;
} }
abstract function javascript();
abstract function php($data); abstract function php($data);
} }

View File

@ -237,7 +237,7 @@ class GridFieldAddExistingAutocompleter implements GridField_HTMLProvider, GridF
return $this->placeholderText; return $this->placeholderText;
} else { } else {
$labels = array(); $labels = array();
foreach($searchFields as $searchField) { if($searchFields) foreach($searchFields as $searchField) {
$label = singleton($dataClass)->fieldLabel($searchField); $label = singleton($dataClass)->fieldLabel($searchField);
if($label) $labels[] = $label; 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; 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); parent::__construct($controller, $name, $fields, $actions);
// Focus on the email input when the page is loaded // Focus on the email input when the page is loaded
// Only include this if other form JS validation is enabled Requirements::customScript(<<<JS
if($this->getValidator()->getJavascriptValidationHandler() != 'none') { (function() {
Requirements::customScript(<<<JS var el = document.getElementById("MemberLoginForm_LoginForm_Email");
(function() { if(el && el.focus) el.focus();
var el = document.getElementById("MemberLoginForm_LoginForm_Email"); })();
if(el && el.focus) el.focus();
})();
JS JS
); );
}
} }
/** /**

View File

@ -5,7 +5,8 @@ class GridFieldDetailFormTest extends FunctionalTest {
protected $extraDataObjects = array( protected $extraDataObjects = array(
'GridFieldDetailFormTest_Person', 'GridFieldDetailFormTest_Person',
'GridFieldDetailFormTest_PeopleGroup' 'GridFieldDetailFormTest_PeopleGroup',
'GridFieldDetailFormTest_Category',
); );
@ -82,6 +83,47 @@ class GridFieldDetailFormTest extends FunctionalTest {
$firstperson = $group->People()->First(); $firstperson = $group->People()->First();
$this->assertEquals($firstperson->Surname, 'Baggins'); $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 { class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
@ -93,6 +135,22 @@ class GridFieldDetailFormTest_Person extends DataObject implements TestOnly {
static $has_one = array( static $has_one = array(
'Group' => 'GridFieldDetailFormTest_PeopleGroup' '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 { class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly {
@ -103,6 +161,40 @@ class GridFieldDetailFormTest_PeopleGroup extends DataObject implements TestOnly
static $has_many = array( static $has_many = array(
'People' => 'GridFieldDetailFormTest_Person' '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 { 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: group:
Name: My Group Name: My Group
People: =>GridFieldDetailFormTest_Person.joe,=>GridFieldDetailFormTest_Person.jane People: =>GridFieldDetailFormTest_Person.joe,=>GridFieldDetailFormTest_Person.jane
GridFieldDetailFormTest_Category:
category1:
Name: Category 1
People: =>GridFieldDetailFormTest_Person.joe,=>GridFieldDetailFormTest_Person.jane