Added JS source map extension to the white map in the .htaccess file. Minor fix in the forms styling. Implemented client-side validation for the Table widget. Updated the Table widget readme file.

This commit is contained in:
alekseybobkov 2015-01-11 13:42:42 -08:00
parent bc287a9559
commit cbc808520d
19 changed files with 664 additions and 12 deletions

View File

@ -22,6 +22,7 @@
##
RewriteCond %{REQUEST_FILENAME} -f
RewriteCond %{REQUEST_URI} !\.js
RewriteCond %{REQUEST_URI} !\.map
RewriteCond %{REQUEST_URI} !\.ico
RewriteCond %{REQUEST_URI} !\.jpg
RewriteCond %{REQUEST_URI} !\.jpeg

View File

@ -9017,6 +9017,7 @@ label {
}
.help-block.before-field {
margin-top: 0;
margin-bottom: 10px;
}
.field-textarea {
resize: vertical;

View File

@ -190,6 +190,7 @@ label {
margin-bottom: 0;
&.before-field {
margin-top: 0;
margin-bottom: 10px;
}
}

View File

@ -127,6 +127,14 @@ class Table extends WidgetBase
public function loadAssets()
{
$this->addCss('css/table.css', 'core');
// Include a combined and minified script.
// TODO: At the moment the files are combined with
// CodeKit 2, but we should have a method to
// combine files without external dependencies. -ab
$this->addJs('js/table-min.js', 'core');
/*
$this->addJs('js/table.js', 'core');
$this->addJs('js/table.helper.navigation.js', 'core');
$this->addJs('js/table.datasource.base.js', 'core');
@ -135,6 +143,14 @@ class Table extends WidgetBase
$this->addJs('js/table.processor.string.js', 'core');
$this->addJs('js/table.processor.checkbox.js', 'core');
$this->addJs('js/table.processor.dropdown.js', 'core');
$this->addJs('js/table.validator.base.js', 'core');
$this->addJs('js/table.validator.required.js', 'core');
$this->addJs('js/table.validator.basenumber.js', 'core');
$this->addJs('js/table.validator.integer.js', 'core');
$this->addJs('js/table.validator.float.js', 'core');
$this->addJs('js/table.validator.length.js', 'core');
$this->addJs('js/table.validator.regex.js', 'core');
*/
}
/**

View File

@ -41,6 +41,7 @@ All classes for the table widget are be defined in the **$.oc.table** namespace.
- **$.oc.table.processor** - cell processors
- **$.oc.table.datasource** - data sources
- **$.oc.table.helper** - helper classes
- **$.oc.table.validator** - validation classes
### Client-side performance and memory usage considerations
@ -271,7 +272,7 @@ public function onSave()
There are two ways to validate the table data - with the client-side and server-side validation.
### Client-side validation
### Client-side validation ($.oc.table.validator)
The client-side validation is performed before the data is sent to the server, or before the user navigates to another page (if the pagination is enabled). Client-side validation is a fast, but simple validation implementation. It can't be used for complex cases like finding duplicating records, or comparing data with records existing in the database.
@ -283,20 +284,107 @@ The client-side validation is configured in the widget configuration file in the
validation:
required:
message: Please select the state
apply_if_has: country
applyIfNotEmpty: country
If a validator doesn't have any options (or default option values should be used), the declaration could look like this:
state:
title: State
type: dropdown
validation:
required: {}
The `applyIfNotEmpty` and `message` parameters are common for all validators.
Currently implemented client-side validation rules:
- required
- integer
- float
- length
- regex
Validation rules can be configured with extra parameters, which depend on a specific validator.
#### required validator
#### required validator ($.oc.table.validator.required)
Checks if the user has provided a value for the cell. Parameters:
Checks if the user has provided a value for the cell.
- message - optional, error message if the message was not provided. If the error message is not provided, the default error message will be displayed.
- apply_if_has - optional, allows to specify a column name which should have a value in order the validator to run. If the column doesn't have a value, the validation won't be performed. If the parameter is omitted, the validation always runs.
#### integer validator ($.oc.table.validator.integer)
Checks if the value is integer. Parameters:
* `allowNegative` - optional, determines if negative values are allowed.
* `min` - optional object, defines the minimum allowed value and error message. Object fields:
* `value` - defines the minimum value.
* `message` - optional, defines the error message.
* `max` - optional object, defines the maximum allowed value and error message. Object fields:
* `value` - defines the maximum value.
* `message` - optional, defines the error message.
Example of defining the integer validator with the `min` parameter:
length:
title: Length
type: string
validation:
integer:
min:
value: 3
message: "The length cannot be less than 3"
#### float validator ($.oc.table.validator.float)
Checks if the value is a floating point number. The parameters for this validator match the parameters of the **integer** validator.
Valid floating point number formats:
* 10
* 10.302
* -10 (if `allowNegative` is `true`)
* -10.84 (if `allowNegative` is `true`)
#### length validator ($.oc.table.validator.length)
Checks if a string is not shorter or longer than specified values. Parameters:
* `min` - optional object, defines the minimum length and error message. Object fields:
* `value` - defines the minimum length.
* `message` - optional, defines the error message.
* `max` - optional object, defines the maximum length and error message. Object fields:
* `value` - defines the maximum length.
* `message` - optional, defines the error message.
Example column definition:
name:
title: Name
type: string
validation:
length:
min:
value: 3
message: "The name is too short."
#### regex validator ($.oc.table.validator.regex)
Checks a string against a provided regular expression:
* `pattern` - specifies the regular expression pattern string. Example: **^[0-9a-z]+$**
* `modifiers` - optional, a string containing regular expression modifiers (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/RegExp), for example **i** for "case insensitive".
Example:
login:
title: Login
type: string
validation:
regex:
pattern: "^[a-z0-9]+$"
modifiers: "i"
message: "The login name can contain only Latin letters and numbers."
Although the `message` parameter is optional for all validators it's highly recommended to provide a message for the regular expression validator as the default message "Invalid value format." is not descriptive and can be confusing.
### Server-side validation

View File

@ -97,6 +97,19 @@
.control-table table.data tr {
background-color: white;
}
.control-table table.data tr.error {
background-color: #fbecec!important;
}
.control-table table.data tr.error td.active.error {
border-color: #ec0000!important;
}
.control-table table.data tr.error td.active.error .content-container {
border-color: #ec0000!important;
}
.control-table table.data tr.error td.active.error .content-container:before,
.control-table table.data tr.error td.active.error .content-container:after {
background-color: #ec0000!important;
}
.control-table table.data tr:nth-child(2n) {
background-color: #fbfbfb;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -147,6 +147,11 @@
}
Navigation.prototype.gotoPage = function(pageIndex, onSuccess) {
this.tableObj.unfocusTable()
if (!this.tableObj.validate())
return
this.pageIndex = pageIndex
this.tableObj.updateDataTable(onSuccess)

View File

@ -428,7 +428,7 @@
Table.prototype.commitEditedRow = function() {
if (this.editedRowKey === null)
return
var editedRow = this.dataTable.querySelector('tr[data-row="'+this.editedRowKey+'"]')
if (!editedRow)
return
@ -492,13 +492,13 @@
if (this.activeCell !== cellElement) {
if (this.activeCell)
this.activeCell.setAttribute('class', '')
this.elementRemoveClass(this.activeCell, 'active')
this.setActiveProcessor(processor)
this.activeCell = cellElement
if (processor.isCellFocusable())
this.activeCell.setAttribute('class', 'active')
this.elementAddClass(this.activeCell, 'active')
}
// If the cell belongs to other row than the currently edited,
@ -536,11 +536,21 @@
currentRowIndex = this.getCellRowIndex(this.activeCell)
}
if (this.navigation.paginationEnabled())
this.navigation.pageIndex = this.navigation.getNewRowPage(placement, currentRowIndex)
this.unfocusTable()
if (this.navigation.paginationEnabled()) {
var newPageIndex = this.navigation.getNewRowPage(placement, currentRowIndex)
if (newPageIndex != this.navigation.pageIndex) {
// Validate data on the current page if adding a new record
// is going to create another page.
if (!this.validate())
return
}
this.navigation.pageIndex = newPageIndex
}
this.recordsAddedOrDeleted++
// New records have negative keys
@ -626,6 +636,52 @@
return this.tableContainer.querySelector('div.toolbar')
}
/*
* Validaates data on the current page
*/
Table.prototype.validate = function() {
var rows = this.dataTable.querySelectorAll('tbody tr[data-row]')
for (var i = 0, len = rows.length; i < len; i++) {
var row = rows[i]
this.elementRemoveClass(row, 'error')
}
for (var i = 0, rowsLen = rows.length; i < rowsLen; i++) {
var row = rows[i],
rowData = this.getRowData(row)
for (var j = 0, colsLen = row.children.length; j < colsLen; j++)
this.elementRemoveClass(row.children[j], 'error')
for (var columnName in rowData) {
var cellProcessor = this.getCellProcessor(columnName),
message = cellProcessor.validate(rowData[columnName], rowData)
if (message !== undefined) {
var cell = row.querySelector('td[data-column="'+columnName+'"]'),
self = this
this.elementAddClass(row, 'error')
this.elementAddClass(cell, 'error')
$.oc.flashMsg({text: message, 'class': 'error'})
window.setTimeout(function(){
self.focusCell(cell, false)
cell = null
self = null
cellProcessor = null
}, 100)
return false
}
}
}
return true
}
// EVENT HANDLERS
// ============================
@ -688,6 +744,11 @@
if (data.handler == this.options.postbackHandlerName) {
this.unfocusTable()
if (!this.validate()) {
ev.preventDefault()
return
}
var fieldName = this.options.alias.indexOf('[') > -1 ?
this.options.alias + '[TableData]' :
this.options.alias + 'TableData';

View File

@ -28,8 +28,13 @@
this.activeCell = null
this.validators = []
// Register event handlers
this.registerHandlers()
// Initialize validators
this.initValidators()
}
Base.prototype.dispose = function() {
@ -175,5 +180,31 @@
return false
}
Base.prototype.initValidators = function() {
if (this.columnConfiguration.validation === undefined)
return
for (var validatorName in this.columnConfiguration.validation) {
if ($.oc.table.validator === undefined || $.oc.table.validator[validatorName] == undefined)
throw new Error('The table cell validator "'+validatorName+'" for the column "'+this.columnName+'" is not ' +
'found in the $.oc.table.validator namespace.')
var validator = new $.oc.table.validator[validatorName](
this.columnConfiguration.validation[validatorName]
)
this.validators.push(validator)
}
}
Base.prototype.validate = function(value, rowData) {
for (var i=0, len=this.validators.length; i<len; i++) {
var message = this.validators[i].validate(value, rowData)
if (message !== undefined)
return message
}
}
$.oc.table.processor.base = Base
}(window.jQuery);

View File

@ -0,0 +1,77 @@
/*
* Base class for the table validators.
*/
+function ($) { "use strict";
// VALIDATOR NAMESPACES
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
$.oc.table.validator = {}
// CLASS DEFINITION
// ============================
var Base = function(options) {
//
// State properties
//
this.options = options
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validate = function(value, rowData) {
if (this.options.applyIfNotEmpty !== undefined && !this.rowHasValue(this.options.applyIfNotEmpty, rowData))
return
return this.validateValue(value, rowData)
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined. This method should be redefined
* in descendant classes.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validateValue = function(value, rowData) {
}
Base.prototype.trim = function(value) {
if (String.prototype.trim)
return value.trim()
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
}
Base.prototype.getMessage = function(defaultValue) {
if (this.options.message !== undefined)
return this.options.message
return defaultValue
}
Base.prototype.rowHasValue = function(columnName, rowData) {
if (rowData[columnName] === undefined)
return false
if (typeof rowData[columnName] == 'boolean')
return rowData[columnName]
var value = this.trim(String(rowData[columnName]))
return value.length > 0
}
$.oc.table.validator.base = Base;
}(window.jQuery);

View File

@ -0,0 +1,57 @@
/*
* Base class for number validators.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.base,
BaseProto = Base.prototype
var BaseNumber = function(options) {
Base.call(this, options)
};
BaseNumber.prototype = Object.create(BaseProto)
BaseNumber.prototype.constructor = BaseNumber
BaseNumber.prototype.doCommonChecks = function(value) {
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the table validator configuration')
if (value < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The value should not be less than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the table validator configuration')
if (value > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The value should not be more than " + this.options.max.value
}
}
}
return
}
$.oc.table.validator.baseNumber = BaseNumber
}(window.jQuery);

View File

@ -0,0 +1,59 @@
/*
* Float table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.oc.table.validator.baseNumber === undefined)
throw new Error("The $.oc.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.baseNumber,
BaseProto = Base.prototype
var Float = function(options) {
Base.call(this, options)
};
Float.prototype = Object.create(BaseProto)
Float.prototype.constructor = Float
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Float.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^[-]?([0-9]+\.[0-9]+|[0-9]+)$/.test(value) :
/^([0-9]+\.[0-9]+|[0-9]+)$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be a floating point number.' :
'The value should be a positive floating point number';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseFloat(value))
}
$.oc.table.validator.float = Float
}(window.jQuery);

View File

@ -0,0 +1,59 @@
/*
* Integer table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.oc.table.validator.baseNumber === undefined)
throw new Error("The $.oc.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.baseNumber,
BaseProto = Base.prototype
var Integer = function(options) {
Base.call(this, options)
};
Integer.prototype = Object.create(BaseProto)
Integer.prototype.constructor = Integer
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Integer.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^\-?[0-9]*$/.test(value) :
/^[0-9]*$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be an integer.' :
'The value should be a positive integer';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseInt(value))
}
$.oc.table.validator.integer = Integer
}(window.jQuery);

View File

@ -0,0 +1,68 @@
/*
* String length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.base,
BaseProto = Base.prototype
var Length = function(options) {
Base.call(this, options)
};
Length.prototype = Object.create(BaseProto)
Length.prototype.constructor = Length
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Length.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the Length table validator configuration')
if (value.length < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The string should not be shorter than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the Length table validator configuration')
if (value.length > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The string should not be longer than " + this.options.max.value
}
}
}
return
}
$.oc.table.validator.length = Length
}(window.jQuery);

View File

@ -0,0 +1,52 @@
/*
* Regex length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.base,
BaseProto = Base.prototype
var Regex = function(options) {
Base.call(this, options)
};
Regex.prototype = Object.create(BaseProto)
Regex.prototype.constructor = Regex
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Regex.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.pattern === undefined)
throw new Error('The pattern parameter is not defined in the Regex table validator configuration')
var regexObj = new RegExp(this.options.pattern, this.options.modifiers)
if (!regexObj.test(value))
return this.getMessage("Invalid value format.")
return
}
$.oc.table.validator.regex = Regex
}(window.jQuery);

View File

@ -0,0 +1,44 @@
/*
* Required table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.validator === undefined)
throw new Error("The $.oc.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.oc.table.validator.base,
BaseProto = Base.prototype
var Required = function(options) {
Base.call(this, options)
};
Required.prototype = Object.create(BaseProto)
Required.prototype.constructor = Required
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Required.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length === 0)
return this.getMessage("The value should not be empty.")
return
}
$.oc.table.validator.required = Required
}(window.jQuery);

View File

@ -120,6 +120,21 @@
tr {
background-color: white;
&.error {
background-color: #fbecec!important;
td.active.error {
border-color: #ec0000!important;
.content-container {
border-color: #ec0000!important;
&:before, &:after {
background-color: #ec0000!important;
}
}
}
}
}
tr:nth-child(2n) {