Implementing Inspector validation + minor refactoring of the Inspector editors.
This commit is contained in:
parent
8d574e93d9
commit
aee247727a
|
|
@ -29,6 +29,7 @@
|
|||
this.parentGroup = group
|
||||
this.group = null // Group created by a grouped editor, for example by the set editor
|
||||
this.childInspector = null
|
||||
this.validators = []
|
||||
|
||||
Base.call(this)
|
||||
|
||||
|
|
@ -39,6 +40,8 @@
|
|||
BaseEditor.prototype.constructor = Base
|
||||
|
||||
BaseEditor.prototype.dispose = function() {
|
||||
this.disposeValidators()
|
||||
|
||||
if (this.childInspector) {
|
||||
this.childInspector.dispose()
|
||||
}
|
||||
|
|
@ -50,6 +53,7 @@
|
|||
this.childInspector = null
|
||||
this.parentGroup = null
|
||||
this.group = null
|
||||
this.validators = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
|
@ -57,6 +61,7 @@
|
|||
BaseEditor.prototype.init = function() {
|
||||
this.build()
|
||||
this.registerHandlers()
|
||||
this.createValidators()
|
||||
}
|
||||
|
||||
BaseEditor.prototype.build = function() {
|
||||
|
|
@ -69,39 +74,13 @@
|
|||
BaseEditor.prototype.onInspectorPropertyChanged = function(property, value) {
|
||||
}
|
||||
|
||||
BaseEditor.prototype.onExternalPropertyEditorHidden = function() {
|
||||
}
|
||||
|
||||
BaseEditor.prototype.focus = function() {
|
||||
}
|
||||
|
||||
BaseEditor.prototype.supportsExternalParameterEditor = function() {
|
||||
return true
|
||||
}
|
||||
|
||||
BaseEditor.prototype.isGroupedEditor = function() {
|
||||
return false
|
||||
}
|
||||
|
||||
BaseEditor.prototype.hasChildSurface = function() {
|
||||
return this.childInspector !== null
|
||||
}
|
||||
|
||||
BaseEditor.prototype.initControlGroup = function() {
|
||||
this.group = this.inspector.getGroupManager().createGroup(this.propertyDefinition.property, this.parentGroup)
|
||||
}
|
||||
|
||||
BaseEditor.prototype.createGroupedRow = function(property) {
|
||||
var row = this.inspector.buildRow(property, this.group),
|
||||
groupedClass = this.inspector.getGroupManager().isGroupExpanded(this.group) ? 'expanded' : 'collapsed'
|
||||
|
||||
this.inspector.applyGroupLevelToRow(row, this.group)
|
||||
|
||||
$.oc.foundation.element.addClass(row, 'property')
|
||||
$.oc.foundation.element.addClass(row, groupedClass)
|
||||
return row
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates displayed value in the editor UI. The value is already set
|
||||
* in the Inspector and should be loaded from Inspector.
|
||||
|
|
@ -117,5 +96,131 @@
|
|||
return this.propertyDefinition.default === undefined ? undefined : this.propertyDefinition.default
|
||||
}
|
||||
|
||||
BaseEditor.prototype.throwError = function(errorMessage) {
|
||||
throw new Error(errorMessage + ' Property: ' + this.propertyDefinition.property)
|
||||
}
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
|
||||
BaseEditor.prototype.createValidators = function() {
|
||||
// Handle legacy validation syntax properties:
|
||||
//
|
||||
// - required
|
||||
// - validationPattern
|
||||
// - validationMessage
|
||||
|
||||
if ((this.propertyDefinition.required !== undefined ||
|
||||
this.propertyDefinition.validationPattern !== undefined ||
|
||||
this.propertyDefinition.validationMessage !== undefined) &&
|
||||
this.propertyDefinition.validation !== undefined) {
|
||||
this.throwError('Legacy and new validation syntax should not be mixed.')
|
||||
}
|
||||
|
||||
if (this.propertyDefinition.required !== undefined) {
|
||||
var validator = new $.oc.inspector.validators.required({
|
||||
message: this.propertyDefinition.validationMessage
|
||||
})
|
||||
|
||||
this.validators.push(validator)
|
||||
}
|
||||
|
||||
if (this.propertyDefinition.validationPattern !== undefined) {
|
||||
var validator = new $.oc.inspector.validators.regex({
|
||||
message: this.propertyDefinition.validationMessage,
|
||||
pattern: this.propertyDefinition.validationPattern
|
||||
})
|
||||
|
||||
this.validators.push(validator)
|
||||
}
|
||||
|
||||
//
|
||||
// Handle new validation syntax
|
||||
//
|
||||
|
||||
if (this.propertyDefinition.validation === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (var validatorName in this.propertyDefinition.validation) {
|
||||
if ($.oc.inspector.validators[validatorName] == undefined) {
|
||||
this.throwError('Inspector validator "' + validatorName + '" is not found in the $.oc.inspector.validators namespace.')
|
||||
}
|
||||
|
||||
var validator = new $.oc.inspector.validators[validatorName](
|
||||
this.propertyDefinition.validation[validatorName]
|
||||
)
|
||||
|
||||
this.validators.push(validator)
|
||||
}
|
||||
}
|
||||
|
||||
BaseEditor.prototype.disposeValidators = function() {
|
||||
for (var i = 0, len = this.validators.length; i < len; i++) {
|
||||
this.validators[i].dispose()
|
||||
}
|
||||
}
|
||||
|
||||
BaseEditor.prototype.validate = function() {
|
||||
for (var i = 0, len = this.validators.length; i < len; i++) {
|
||||
var validator = this.validators[i],
|
||||
value = this.inspector.getPropertyValue(this.propertyDefinition.property)
|
||||
|
||||
if (value === undefined) {
|
||||
value = this.getUndefinedValue()
|
||||
}
|
||||
|
||||
if (!validator.isValid(value)) {
|
||||
$.oc.flashMsg({text: validator.getMessage(), 'class': 'error', 'interval': 5})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
BaseEditor.prototype.markInvalid = function() {
|
||||
$.oc.foundation.element.addClass(this.containerRow, 'invalid')
|
||||
this.inspector.getGroupManager().markGroupRowInvalid(this.parentGroup, this.inspector.getRootTable())
|
||||
|
||||
this.inspector.getRootSurface().expandGroupParents(this.parentGroup)
|
||||
this.focus()
|
||||
}
|
||||
|
||||
//
|
||||
// External editor
|
||||
//
|
||||
|
||||
BaseEditor.prototype.supportsExternalParameterEditor = function() {
|
||||
return true
|
||||
}
|
||||
|
||||
BaseEditor.prototype.onExternalPropertyEditorHidden = function() {
|
||||
}
|
||||
|
||||
//
|
||||
// Grouping
|
||||
//
|
||||
|
||||
BaseEditor.prototype.isGroupedEditor = function() {
|
||||
return false
|
||||
}
|
||||
|
||||
BaseEditor.prototype.initControlGroup = function() {
|
||||
this.group = this.inspector.getGroupManager().createGroup(this.propertyDefinition.property, this.parentGroup)
|
||||
}
|
||||
|
||||
BaseEditor.prototype.createGroupedRow = function(property) {
|
||||
var row = this.inspector.buildRow(property, this.group),
|
||||
groupedClass = this.inspector.getGroupManager().isGroupExpanded(this.group) ? 'expanded' : 'collapsed'
|
||||
|
||||
this.inspector.applyGroupLevelToRow(row, this.group)
|
||||
|
||||
$.oc.foundation.element.addClass(row, 'property')
|
||||
$.oc.foundation.element.addClass(row, groupedClass)
|
||||
return row
|
||||
}
|
||||
|
||||
$.oc.inspector.propertyEditors.base = BaseEditor
|
||||
}(window.jQuery);
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
var itemCount = 0
|
||||
|
||||
if (typeof value !== 'object') {
|
||||
throw new Error('Object list value should be an object. Property: ' + this.propertyDefinition.property)
|
||||
this.throwError('Object list value should be an object.')
|
||||
}
|
||||
|
||||
itemCount = this.getValueKeys(value).length
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
var ObjectEditor = function(inspector, propertyDefinition, containerCell, group) {
|
||||
if (propertyDefinition.properties === undefined) {
|
||||
throw new Error('The properties property should be specified in the object editor configuration. Property: ' + propertyDefinition.property)
|
||||
this.throwError('The properties property should be specified in the object editor configuration.')
|
||||
}
|
||||
|
||||
Base.call(this, inspector, propertyDefinition, containerCell, group)
|
||||
|
|
|
|||
|
|
@ -488,7 +488,7 @@
|
|||
var form = this.popup.querySelector('form')
|
||||
|
||||
if (!form) {
|
||||
throw new Error('Cannot find form element in the popup window.')
|
||||
this.throwError('Cannot find form element in the popup window.')
|
||||
}
|
||||
|
||||
return form
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
StringListEditor.prototype.checkValueType = function(value) {
|
||||
if (value && Object.prototype.toString.call(value) !== '[object Array]') {
|
||||
throw new Error('The string list value should be an array.')
|
||||
this.throwError('The string list value should be an array.')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -241,6 +241,19 @@
|
|||
this.getInput().focus()
|
||||
}
|
||||
|
||||
ExternalParameterEditor.prototype.validate = function() {
|
||||
var value = $.trim(this.getValue())
|
||||
|
||||
if (value.length === 0) {
|
||||
$.oc.flashMsg({text: 'Please enter the external parameter name.', 'class': 'error', 'interval': 5})
|
||||
this.focus()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// Event handlers
|
||||
//
|
||||
|
|
|
|||
|
|
@ -103,6 +103,27 @@
|
|||
return group.findGroupRows(table, ignoreCollapsedSubgroups, this)
|
||||
}
|
||||
|
||||
GroupManager.prototype.markGroupRowInvalid = function(group, table) {
|
||||
var currentGroup = group
|
||||
|
||||
while (currentGroup) {
|
||||
var row = currentGroup.findGroupRow(table)
|
||||
if (row) {
|
||||
$.oc.foundation.element.addClass(row, 'invalid')
|
||||
}
|
||||
|
||||
currentGroup = currentGroup.parentGroup
|
||||
}
|
||||
}
|
||||
|
||||
GroupManager.prototype.unmarkInvalidGroups = function(table) {
|
||||
var rows = table.querySelectorAll('tr.invalid')
|
||||
|
||||
for (var i = rows.length-1; i >= 0; i--) {
|
||||
$.oc.foundation.element.removeClass(rows[i], 'invalid')
|
||||
}
|
||||
}
|
||||
|
||||
GroupManager.prototype.isRowVisible = function(table, rowGroupIndex) {
|
||||
var group = this.findGroupByIndex(index)
|
||||
|
||||
|
|
@ -204,6 +225,19 @@
|
|||
return level
|
||||
}
|
||||
|
||||
Group.prototype.getGroupAndAllParents = function() {
|
||||
var current = this,
|
||||
result = []
|
||||
|
||||
while (current) {
|
||||
result.push(current)
|
||||
|
||||
current = current.parentGroup
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Group.prototype.findGroupRows = function(table, ignoreCollapsedSubgroups, groupManager) {
|
||||
var groupIndex = this.getGroupIndex(),
|
||||
rows = table.querySelectorAll('tr[data-parent-group-index="'+groupIndex+'"]'),
|
||||
|
|
@ -225,5 +259,9 @@
|
|||
return result
|
||||
}
|
||||
|
||||
Group.prototype.findGroupRow = function(table) {
|
||||
return table.querySelector('tr[data-group-index="'+this.groupIndex+'"]')
|
||||
}
|
||||
|
||||
$.oc.inspector.groupManager = GroupManager
|
||||
}(window.jQuery);
|
||||
|
|
@ -390,14 +390,14 @@
|
|||
th.children[0].style.marginLeft = groupLevel*10 + 'px'
|
||||
}
|
||||
|
||||
Surface.prototype.toggleGroup = function(row) {
|
||||
Surface.prototype.toggleGroup = function(row, forceExpand) {
|
||||
var link = row.querySelector('a'),
|
||||
groupIndex = row.getAttribute('data-group-index'),
|
||||
table = this.getRootTable(),
|
||||
groupManager = this.getGroupManager(),
|
||||
collapse = true
|
||||
|
||||
if ($.oc.foundation.element.hasClass(link, 'expanded')) {
|
||||
if ($.oc.foundation.element.hasClass(link, 'expanded') && !forceExpand) {
|
||||
$.oc.foundation.element.removeClass(link, 'expanded')
|
||||
} else {
|
||||
$.oc.foundation.element.addClass(link, 'expanded')
|
||||
|
|
@ -407,21 +407,41 @@
|
|||
var propertyRows = groupManager.findGroupRows(table, groupIndex, !collapse),
|
||||
duration = Math.round(50 / propertyRows.length)
|
||||
|
||||
this.expandOrCollapseRows(propertyRows, collapse, duration)
|
||||
this.expandOrCollapseRows(propertyRows, collapse, duration, forceExpand)
|
||||
groupManager.setGroupStatus(groupIndex, !collapse)
|
||||
}
|
||||
|
||||
Surface.prototype.expandOrCollapseRows = function(rows, collapse, duration) {
|
||||
Surface.prototype.expandGroupParents = function(group) {
|
||||
var groups = group.getGroupAndAllParents(),
|
||||
table = this.getRootTable()
|
||||
|
||||
for (var i = groups.length-1; i >= 0; i--) {
|
||||
var row = groups[i].findGroupRow(table)
|
||||
|
||||
if (row) {
|
||||
this.toggleGroup(row, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Surface.prototype.expandOrCollapseRows = function(rows, collapse, duration, noAnimation) {
|
||||
var row = rows.pop(),
|
||||
self = this
|
||||
|
||||
if (row) {
|
||||
setTimeout(function toggleRow() {
|
||||
if (!noAnimation) {
|
||||
setTimeout(function toggleRow() {
|
||||
$.oc.foundation.element.toggleClass(row, 'collapsed', collapse)
|
||||
$.oc.foundation.element.toggleClass(row, 'expanded', !collapse)
|
||||
|
||||
self.expandOrCollapseRows(rows, collapse, duration, noAnimation)
|
||||
}, duration)
|
||||
} else {
|
||||
$.oc.foundation.element.toggleClass(row, 'collapsed', collapse)
|
||||
$.oc.foundation.element.toggleClass(row, 'expanded', !collapse)
|
||||
|
||||
self.expandOrCollapseRows(rows, collapse, duration)
|
||||
}, duration)
|
||||
self.expandOrCollapseRows(rows, collapse, duration, noAnimation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -708,10 +728,6 @@
|
|||
Surface.prototype.getValues = function() {
|
||||
var result = {}
|
||||
|
||||
// TODO: implement validation in this method. It should be optional,
|
||||
// as the method is used by other classes internally, but the validation
|
||||
// is required only for the external callers.
|
||||
|
||||
for (var i=0, len = this.parsedProperties.properties.length; i < len; i++) {
|
||||
var property = this.parsedProperties.properties[i]
|
||||
|
||||
|
|
@ -751,6 +767,32 @@
|
|||
return result
|
||||
}
|
||||
|
||||
Surface.prototype.validate = function() {
|
||||
this.getGroupManager().unmarkInvalidGroups(this.getRootTable())
|
||||
|
||||
for (var i = 0, len = this.editors.length; i < len; i++) {
|
||||
var editor = this.editors[i],
|
||||
externalEditor = this.findExternalParameterEditor(editor.propertyDefinition.property)
|
||||
|
||||
if (externalEditor && externalEditor.isEditorVisible()) {
|
||||
if (!externalEditor.validate()) {
|
||||
editor.markInvalid()
|
||||
return false
|
||||
}
|
||||
else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!editor.validate()) {
|
||||
editor.markInvalid()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// EVENT HANDLERS
|
||||
//
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Inspector validator base class.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// NAMESPACES
|
||||
// ============================
|
||||
|
||||
if ($.oc.inspector.validators === undefined)
|
||||
$.oc.inspector.validators = {}
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.foundation.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var BaseValidator = function(options) {
|
||||
this.options = options
|
||||
this.defaultMessage = 'Invalid property value'
|
||||
}
|
||||
|
||||
BaseValidator.prototype = Object.create(BaseProto)
|
||||
BaseValidator.prototype.constructor = Base
|
||||
|
||||
BaseValidator.prototype.dispose = function() {
|
||||
this.defaultMessage = null
|
||||
|
||||
BaseProto.dispose.call(this)
|
||||
}
|
||||
|
||||
BaseValidator.prototype.getMessage = function() {
|
||||
if (this.options.message !== undefined)
|
||||
return this.options.message
|
||||
|
||||
return this.defaultMessage
|
||||
}
|
||||
|
||||
BaseValidator.prototype.isScalar = function(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || typeof value == 'number' || typeof value == 'boolean') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
BaseValidator.prototype.isValid = function(value) {
|
||||
return true
|
||||
}
|
||||
|
||||
$.oc.inspector.validators.base = BaseValidator
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Inspector regex validator.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
var Base = $.oc.inspector.validators.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var RegexValidator = function(options) {
|
||||
Base.call(this, options)
|
||||
}
|
||||
|
||||
RegexValidator.prototype = Object.create(BaseProto)
|
||||
RegexValidator.prototype.constructor = Base
|
||||
|
||||
RegexValidator.prototype.isValid = function(value) {
|
||||
if (!this.isScalar(value)) {
|
||||
this.throwError('The Regex Inspector validator can only be used with string values.')
|
||||
}
|
||||
|
||||
if (value === undefined || value === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
var string = String(value)
|
||||
|
||||
if (string.length == 0)
|
||||
return
|
||||
|
||||
if (this.options.pattern === undefined) {
|
||||
this.throwError('The pattern parameter is not defined in the Regex Inspector validator configuration.')
|
||||
}
|
||||
|
||||
var regexObj = new RegExp(this.options.pattern, this.options.modifiers)
|
||||
|
||||
return regexObj.test(string)
|
||||
}
|
||||
|
||||
$.oc.inspector.validators.regex = RegexValidator
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Inspector required validator.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
var Base = $.oc.inspector.validators.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var RequiredValidator = function(options) {
|
||||
Base.call(this, options)
|
||||
}
|
||||
|
||||
RequiredValidator.prototype = Object.create(BaseProto)
|
||||
RequiredValidator.prototype.constructor = Base
|
||||
|
||||
RequiredValidator.prototype.isValid = function(value) {
|
||||
if (value === undefined || value === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return !$.isEmptyObject(value)
|
||||
}
|
||||
|
||||
return $.trim(String(value)).length > 0
|
||||
}
|
||||
|
||||
$.oc.inspector.validators.required = RequiredValidator
|
||||
}(window.jQuery);
|
||||
|
|
@ -58,6 +58,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
tr.invalid th {
|
||||
color: #c03f31!important;
|
||||
}
|
||||
|
||||
tr.control-group {
|
||||
.user-select(none);
|
||||
|
||||
|
|
|
|||
|
|
@ -2429,6 +2429,7 @@ table.table.data tr.list-tree-level-10 td.list-cell-index-1{padding-left:125px}
|
|||
.inspector-fields tr:last-child td,.inspector-fields tr:last-child td input[type=text]{-webkit-border-radius:0 0 2px 0;-moz-border-radius:0 0 2px 0;border-radius:0 0 2px 0}
|
||||
.inspector-fields tr.group{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.inspector-fields tr.group th{background:#e0e4e5;font-weight:600;cursor:pointer}
|
||||
.inspector-fields tr.invalid th{color:#c03f31 !important}
|
||||
.inspector-fields tr.control-group{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
|
||||
.inspector-fields tr.control-group th,.inspector-fields tr.control-group td{cursor:pointer}
|
||||
.inspector-fields tr.collapsed{display:none}
|
||||
|
|
|
|||
Loading…
Reference in New Issue