Merge branch 'mbruce/textArea-refactor-entryfield' into 'develop'
Refactored to use validator See merge request BPHV_MIPS/vds_ios!192
This commit is contained in:
commit
0289764065
@ -13,7 +13,7 @@ import Combine
|
||||
|
||||
/// Base Class used to build out a Input controls.
|
||||
@objc(VDSEntryField)
|
||||
open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
@ -153,8 +153,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
/// Whether not to show the error.
|
||||
open var showError: Bool = false { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// FormFieldValidator
|
||||
internal var validator: (any FormFieldValidatorable)?
|
||||
|
||||
/// Whether or not to show the internal error
|
||||
open internal(set) var hasInternalError: Bool = false { didSet { setNeedsUpdate() } }
|
||||
open var hasInternalError: Bool { !(validator?.isValid ?? true) }
|
||||
|
||||
/// Override UIControl state to add the .error state if showError is true.
|
||||
open override var state: UIControl.State {
|
||||
@ -175,7 +178,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
}
|
||||
}
|
||||
|
||||
internal var internalErrorText: String? {
|
||||
open var internalErrorText: String? {
|
||||
didSet {
|
||||
updateContainerView()
|
||||
updateErrorLabel()
|
||||
@ -200,19 +203,18 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
open var inputId: String? { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// The text of this textField.
|
||||
private var _value: AnyHashable?
|
||||
open var value: AnyHashable? {
|
||||
private var _value: String?
|
||||
open var value: String? {
|
||||
get { _value }
|
||||
set {
|
||||
if let newValue, newValue != _value {
|
||||
_value = newValue
|
||||
text = newValue as? String
|
||||
text = newValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() } }
|
||||
|
||||
open var required: Bool = false { didSet { setNeedsUpdate() } }
|
||||
@ -324,6 +326,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldable {
|
||||
updateHelperLabel()
|
||||
|
||||
backgroundColor = surface.color
|
||||
validator?.validate()
|
||||
internalErrorText = validator?.errorMessage
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -89,7 +89,7 @@ open class InputField: EntryFieldBase, UITextFieldDelegate {
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var _showError: Bool = false
|
||||
/// Whether not to show the error.
|
||||
open override var showError: Bool {
|
||||
|
||||
@ -107,17 +107,19 @@ open class TextArea: EntryFieldBase {
|
||||
}
|
||||
|
||||
/// The text of this textView
|
||||
private var _text: String?
|
||||
open override var text: String? {
|
||||
get { textView.text }
|
||||
set {
|
||||
if let newValue, newValue != text {
|
||||
if let newValue, newValue != _text {
|
||||
_text = newValue
|
||||
textView.text = newValue
|
||||
value = newValue
|
||||
}
|
||||
setNeedsUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// UITextView shown in the TextArea.
|
||||
open var textView = TextView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
@ -125,6 +127,8 @@ open class TextArea: EntryFieldBase {
|
||||
$0.isScrollEnabled = false
|
||||
}
|
||||
|
||||
open override var maxLength: Int? { willSet { countRule.maxLength = newValue }}
|
||||
|
||||
/// Color configuration for error icon.
|
||||
internal var iconColorConfiguration = ControlColorConfiguration().with {
|
||||
$0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal)
|
||||
@ -147,6 +151,7 @@ open class TextArea: EntryFieldBase {
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
accessibilityLabel = "TextArea"
|
||||
validator = FormFieldValidator<TextArea>(field: self, rules: [.init(countRule)])
|
||||
|
||||
containerStackView.pinToSuperView(.uniform(VDSFormControls.spaceInset))
|
||||
minWidthConstraint = containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: containerSize.width)
|
||||
@ -187,7 +192,6 @@ open class TextArea: EntryFieldBase {
|
||||
/// Used to make changes to the View based off a change events or from local properties.
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
|
||||
textView.isEditable = isEnabled
|
||||
textView.isEnabled = isEnabled
|
||||
textView.surface = surface
|
||||
@ -202,13 +206,8 @@ open class TextArea: EntryFieldBase {
|
||||
widthConstraint?.isActive = false
|
||||
minWidthConstraint?.isActive = true
|
||||
}
|
||||
|
||||
let characterError = getCharacterCounterText()
|
||||
if let maxLength, maxLength > 0 {
|
||||
characterCounterLabel.text = characterError
|
||||
} else {
|
||||
characterCounterLabel.text = ""
|
||||
}
|
||||
|
||||
characterCounterLabel.text = getCharacterCounterText()
|
||||
|
||||
icon.size = .medium
|
||||
icon.color = iconColorConfiguration.getColor(self)
|
||||
@ -247,17 +246,11 @@ open class TextArea: EntryFieldBase {
|
||||
let countStr = (count > maxLength ?? 0) ? ("-" + "\(count-(maxLength ?? 0))") : "\(count)"
|
||||
if let maxLength, maxLength > 0 {
|
||||
if count > maxLength {
|
||||
hasInternalError = true
|
||||
internalErrorText = "You have exceeded the character limit."
|
||||
return countStr
|
||||
} else {
|
||||
hasInternalError = false
|
||||
internalErrorText = nil
|
||||
return ("\(countStr)" + "/" + "\(maxLength)")
|
||||
}
|
||||
} else {
|
||||
hasInternalError = false
|
||||
internalErrorText = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -277,6 +270,21 @@ open class TextArea: EntryFieldBase {
|
||||
|
||||
textView.textAttributes = textAttributes
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Validation
|
||||
//--------------------------------------------------
|
||||
var countRule = CharacterCountRule()
|
||||
|
||||
class CharacterCountRule: Rule {
|
||||
var maxLength: Int?
|
||||
var errorMessage: String = "You have exceeded the character limit."
|
||||
|
||||
func isValid(value: String?) -> Bool {
|
||||
guard let text = value, let maxLength, maxLength > 0 else { return true }
|
||||
return text.count <= maxLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TextArea: UITextViewDelegate {
|
||||
|
||||
@ -9,10 +9,91 @@ import Foundation
|
||||
|
||||
/// Protocol used for a FormField object.
|
||||
public protocol FormFieldable {
|
||||
associatedtype ValueType = AnyHashable
|
||||
|
||||
/// Unique Id for the Form Field object within a Form.
|
||||
var inputId: String? { get set }
|
||||
|
||||
/// Value for the Form Field.
|
||||
var value: AnyHashable? { get set }
|
||||
var value: ValueType? { get set }
|
||||
}
|
||||
|
||||
/// Protocol for FormFieldable that require internal validation.
|
||||
public protocol FormFieldInternalValidatable: FormFieldable {
|
||||
/// Is there an internalError
|
||||
var hasInternalError: Bool { get }
|
||||
/// Internal Error Message that will show.
|
||||
var internalErrorText: String? { get }
|
||||
}
|
||||
|
||||
/// Struct that will execute the validation.
|
||||
public protocol FormFieldValidatorable {
|
||||
associatedtype FieldType: FormFieldable
|
||||
/// FormFieldable to be validated.
|
||||
var field: FieldType { get set }
|
||||
/// Rules that will be applied against the FormFieldable
|
||||
var rules: [AnyRule<FieldType.ValueType>] { get set }
|
||||
/// Error Message that will show.
|
||||
var errorMessage: String? { get }
|
||||
/// Is the FormField valid.
|
||||
var isValid: Bool { get }
|
||||
/// Run the rules against the FormFieldable.
|
||||
func validate()
|
||||
}
|
||||
|
||||
/// Rule that will be executed against a specific ValueType.
|
||||
public protocol Rule<ValueType> {
|
||||
associatedtype ValueType
|
||||
/// Determines if this rule valid for the value passed.
|
||||
func isValid(value: ValueType?) -> Bool
|
||||
/// Error Message to be show if the value is invalid.
|
||||
var errorMessage: String { get }
|
||||
}
|
||||
|
||||
/// Type Erased Rule for a specific ValueType.
|
||||
public struct AnyRule<ValueType>: Rule {
|
||||
private let _isValid: (ValueType?) -> Bool
|
||||
|
||||
public let errorMessage: String
|
||||
|
||||
public init<R: Rule>(_ rule: R) where R.ValueType == ValueType {
|
||||
self._isValid = rule.isValid
|
||||
self.errorMessage = rule.errorMessage
|
||||
}
|
||||
|
||||
public func isValid(value: ValueType?) -> Bool {
|
||||
return _isValid(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic Validator for a specific FormFieldable.
|
||||
public class FormFieldValidator<Field:FormFieldable>: FormFieldValidatorable{
|
||||
public var field: Field
|
||||
public var rules: [AnyRule<Field.ValueType>]
|
||||
public var errorMessages = [String]()
|
||||
public var isValid: Bool = true
|
||||
|
||||
public init(field: Field, rules: [AnyRule<Field.ValueType>]) {
|
||||
self.field = field
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
public var errorMessage: String? {
|
||||
guard errorMessages.count > 0 else { return nil }
|
||||
return errorMessages.joined(separator: "\r")
|
||||
}
|
||||
|
||||
public func validate() {
|
||||
errorMessages.removeAll()
|
||||
|
||||
for rule in rules {
|
||||
if !rule.isValid(value: field.value) {
|
||||
errorMessages.append(rule.errorMessage)
|
||||
isValid = false
|
||||
return
|
||||
}
|
||||
}
|
||||
isValid = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user