FormFieldEffects Update
This commit is contained in:
parent
12331e284b
commit
87195296cc
@ -571,6 +571,13 @@
|
||||
DBEFFA04225A829700230692 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB891E822253FA8500022516 /* Label.swift */; };
|
||||
EA5124FD243601600051A3A4 /* BGImageHeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */; };
|
||||
EA5124FF2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */; };
|
||||
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */; };
|
||||
EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */; };
|
||||
EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */; };
|
||||
EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */; };
|
||||
EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */; };
|
||||
EABFC1412763BB8D00E78B40 /* StateLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* StateLabel.swift */; };
|
||||
EABFC152276913E800E78B40 /* StateLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* StateLabelModel.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -1141,6 +1148,13 @@
|
||||
DBC4391A224421A0001AB423 /* CaretLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretLink.swift; sourceTree = "<group>"; };
|
||||
EA5124FC243601600051A3A4 /* BGImageHeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButton.swift; sourceTree = "<group>"; };
|
||||
EA5124FE2436018E0051A3A4 /* BGImageHeadlineBodyButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGImageHeadlineBodyButtonModel.swift; sourceTree = "<group>"; };
|
||||
EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableFormFieldEffectModel.swift; sourceTree = "<group>"; };
|
||||
EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIUpdatableModelProtocol.swift; sourceTree = "<group>"; };
|
||||
EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormFieldEffectProtocol.swift; sourceTree = "<group>"; };
|
||||
EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideFormFieldEffectModel.swift; sourceTree = "<group>"; };
|
||||
EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableFormFieldEffectModel.swift; sourceTree = "<group>"; };
|
||||
EABFC1402763BB8D00E78B40 /* StateLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateLabel.swift; sourceTree = "<group>"; };
|
||||
EABFC151276913E800E78B40 /* StateLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateLabelModel.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -1206,6 +1220,7 @@
|
||||
01C74D87224298E2009C25A3 /* FormUIHelpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */,
|
||||
011D95882404249B000E3791 /* FormHolderModelProtocol.swift */,
|
||||
011D95AC2406BB57000E3791 /* FormHolderProtocol.swift */,
|
||||
011D95AA2405C553000E3791 /* FormItemProtocol.swift */,
|
||||
@ -1213,6 +1228,7 @@
|
||||
011D95A824057AC7000E3791 /* FormGroupWatcherFieldProtocol.swift */,
|
||||
011D9601240DA20A000E3791 /* FormRuleWatcherFieldProtocol.swift */,
|
||||
0105618A224BBE7700E1557D /* FormValidator.swift */,
|
||||
EA7E67752758365300ABF773 /* UIUpdatableModelProtocol.swift */,
|
||||
011D958A24042794000E3791 /* Rules */,
|
||||
);
|
||||
path = FormUIHelpers;
|
||||
@ -1428,6 +1444,8 @@
|
||||
94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */,
|
||||
94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */,
|
||||
DB891E822253FA8500022516 /* Label.swift */,
|
||||
EABFC151276913E800E78B40 /* StateLabelModel.swift */,
|
||||
EABFC1402763BB8D00E78B40 /* StateLabel.swift */,
|
||||
);
|
||||
path = Label;
|
||||
sourceTree = "<group>";
|
||||
@ -2400,6 +2418,17 @@
|
||||
path = Alerts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAA0CFAD275E7D5A00D65EB0 /* FormFieldEffect */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAA0CFAE275E7D8000D65EB0 /* FormFieldEffectProtocol.swift */,
|
||||
EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */,
|
||||
EAA0CFB2275E831E00D65EB0 /* DisableFormFieldEffectModel.swift */,
|
||||
EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */,
|
||||
);
|
||||
path = FormFieldEffect;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@ -2538,6 +2567,7 @@
|
||||
324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */,
|
||||
5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */,
|
||||
AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */,
|
||||
EABFC152276913E800E78B40 /* StateLabelModel.swift in Sources */,
|
||||
0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */,
|
||||
0AF60F0926B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift in Sources */,
|
||||
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */,
|
||||
@ -2606,6 +2636,7 @@
|
||||
D20C700B250BFDE40095B21C /* MVMCoreUITopAlertView+Extension.swift in Sources */,
|
||||
D236E5B4241FEB1000C38625 /* ListTwoColumnPriceDescription.swift in Sources */,
|
||||
0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */,
|
||||
EA7E67742758310500ABF773 /* EnableFormFieldEffectModel.swift in Sources */,
|
||||
D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */,
|
||||
942C378C2412F4FA0066E45E /* ModalMoleculeListTemplate.swift in Sources */,
|
||||
BB47A588241615FA002BB23C /* ListOneColumnFullWidthTextDividerSubsection.swift in Sources */,
|
||||
@ -2748,6 +2779,7 @@
|
||||
D2CAC7CD251104FE00C75681 /* NotificationModel.swift in Sources */,
|
||||
D2ED2803254B0E0300A1C293 /* MVMCoreAlertHandler.m in Sources */,
|
||||
0A1B4A96233BB18F005B3FB4 /* CheckboxLabel.swift in Sources */,
|
||||
EAA0CFAF275E7D8000D65EB0 /* FormFieldEffectProtocol.swift in Sources */,
|
||||
D20923592450ECE00044AD09 /* TableView.swift in Sources */,
|
||||
BB47A586241615EF002BB23C /* ListOneColumnFullWidthTextDividerSubsectionModel.swift in Sources */,
|
||||
AA69AAF82445BF6800AF3D3B /* ListLeftVariableCheckboxBodyTextModel.swift in Sources */,
|
||||
@ -2959,6 +2991,7 @@
|
||||
013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */,
|
||||
AA104AC724472DB0004D2810 /* HeadersH1Button.swift in Sources */,
|
||||
525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */,
|
||||
EABFC1412763BB8D00E78B40 /* StateLabel.swift in Sources */,
|
||||
AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */,
|
||||
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
|
||||
D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */,
|
||||
@ -2968,12 +3001,14 @@
|
||||
27F973532466074500CAB5C5 /* PageBehaviorProtocol.swift in Sources */,
|
||||
94C2D9A323872C110006CF46 /* LabelAttributeStrikeThroughModel.swift in Sources */,
|
||||
D28A838523CCCA8900DFE4FC /* ScrollerModel.swift in Sources */,
|
||||
EAA0CFB3275E831E00D65EB0 /* DisableFormFieldEffectModel.swift in Sources */,
|
||||
D29DF26C21E6AA0B003B2FB9 /* FLAnimatedImage.m in Sources */,
|
||||
D23EA7FE247EBBB700D60C34 /* NavigationLabelButtonModel.swift in Sources */,
|
||||
D28A839123CD4FD400DFE4FC /* CornerLabelsModel.swift in Sources */,
|
||||
012A88F123985E0100FE3DA1 /* Color.swift in Sources */,
|
||||
D22D8393241C27B100D3DF69 /* TemplateModel.swift in Sources */,
|
||||
012A889C23889E8400FE3DA1 /* TemplateModelProtocol.swift in Sources */,
|
||||
EAA0CFB1275E823A00D65EB0 /* HideFormFieldEffectModel.swift in Sources */,
|
||||
D23A8FFB26123189007E14CE /* PageBehaviorModelProtocol.swift in Sources */,
|
||||
52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */,
|
||||
D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */,
|
||||
@ -3040,6 +3075,7 @@
|
||||
011D95AD2406BB57000E3791 /* FormHolderProtocol.swift in Sources */,
|
||||
D23A9004261234CE007E14CE /* PageBehaviorHandlerProtocol.swift in Sources */,
|
||||
01509D932327ECFB00EF99AA /* ProgressBar.swift in Sources */,
|
||||
EA7E67762758365300ABF773 /* UIUpdatableModelProtocol.swift in Sources */,
|
||||
D2169301251E51E7002A6324 /* SectionListTemplate.swift in Sources */,
|
||||
0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */,
|
||||
D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */,
|
||||
|
||||
@ -11,7 +11,7 @@ import UIKit
|
||||
public typealias FacadeElements = (fill: UIColor?, text: UIColor?, border: UIColor?)
|
||||
|
||||
|
||||
open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol {
|
||||
open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -59,12 +59,6 @@ open class ButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWat
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
public func setValidity(_ valid: Bool, group: FormGroupRule) {
|
||||
enabled = valid
|
||||
updateUI?()
|
||||
}
|
||||
|
||||
/// Temporary binding mechanism for the view to update on enable changes.
|
||||
public var updateUI: ActionBlock?
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol, EnableableModelProtocol {
|
||||
open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGroupWatcherFieldProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -25,11 +25,6 @@ open class ImageButtonModel: ButtonModelProtocol, MoleculeModelProtocol, FormGro
|
||||
public var disabledTintColor: Color?
|
||||
|
||||
public var groupName: String = ""
|
||||
|
||||
public func setValidity(_ valid: Bool, group: FormGroupRule) {
|
||||
enabled = valid
|
||||
updateUI?()
|
||||
}
|
||||
|
||||
public var updateUI: ActionBlock?
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
//--------------------------------------------------
|
||||
|
||||
public override func formFieldValue() -> AnyHashable? {
|
||||
guard enabled else { return nil }
|
||||
guard !options.isEmpty,
|
||||
let index = selectedIndex
|
||||
else { return nil }
|
||||
|
||||
@ -25,7 +25,7 @@ import Foundation
|
||||
//--------------------------------------------------
|
||||
|
||||
public override func formFieldValue() -> AnyHashable? {
|
||||
|
||||
guard enabled else { return nil }
|
||||
guard !components.isEmpty && !selectedIndexes.isEmpty else { return nil }
|
||||
|
||||
return selectedRowText
|
||||
|
||||
@ -17,11 +17,8 @@ import UIKit
|
||||
//--------------------------------------------------
|
||||
// MARK: - Outlets
|
||||
//--------------------------------------------------
|
||||
|
||||
public private(set) var titleLabel: Label = {
|
||||
let label = Label()
|
||||
label.font = Styler.Font.RegularMicro.getFont()
|
||||
label.textColor = .mvmBlack
|
||||
public private(set) var titleLabel: StateLabel = {
|
||||
let label = StateLabel()
|
||||
label.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
return label
|
||||
}()
|
||||
@ -29,10 +26,8 @@ import UIKit
|
||||
public private(set) var entryFieldContainer = EntryFieldContainer()
|
||||
|
||||
/// Provides contextual information on the TextField.
|
||||
public private(set) var feedbackLabel: Label = {
|
||||
let label = Label()
|
||||
label.font = Styler.Font.RegularMicro.getFont()
|
||||
label.textColor = .mvmBlack
|
||||
public private(set) var feedbackLabel: StateLabel = {
|
||||
let label = StateLabel()
|
||||
label.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
return label
|
||||
}()
|
||||
@ -46,7 +41,6 @@ import UIKit
|
||||
//--------------------------------------------------
|
||||
// MARK: - Stored Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public var isValid: Bool = false
|
||||
|
||||
/// Validate on each entry in the textField. Default: true
|
||||
@ -60,10 +54,15 @@ import UIKit
|
||||
public var isEnabled: Bool {
|
||||
get { entryFieldContainer.isEnabled }
|
||||
set (enabled) {
|
||||
titleLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
|
||||
feedbackLabel.textColor = enabled ? .mvmBlack : .mvmCoolGray3
|
||||
entryFieldContainer.isEnabled = enabled
|
||||
entryFieldModel?.enabled = enabled
|
||||
if(entryFieldContainer.isEnabled != enabled){
|
||||
titleLabel.isEnabled = enabled
|
||||
feedbackLabel.isEnabled = enabled
|
||||
entryFieldContainer.isEnabled = enabled
|
||||
entryFieldModel?.enabled = enabled
|
||||
if !enabled {
|
||||
self.text = defaultText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,8 +70,11 @@ import UIKit
|
||||
public var showError: Bool {
|
||||
get { entryFieldContainer.showError }
|
||||
set (error) {
|
||||
feedback = error ? errorMessage : entryFieldModel?.feedback
|
||||
feedbackLabel.textColor = error ? entryFieldModel?.errorTextColor?.uiColor ?? .mvmBlack : .mvmBlack
|
||||
if error {
|
||||
feedbackLabel.showError(message: self.errorMessage ?? "")
|
||||
} else {
|
||||
feedbackLabel.reset()
|
||||
}
|
||||
entryFieldContainer.showError = error
|
||||
entryFieldModel?.showError = error
|
||||
}
|
||||
@ -104,11 +106,11 @@ import UIKit
|
||||
public var title: String? {
|
||||
get { titleLabel.text }
|
||||
set {
|
||||
titleLabel.text = newValue
|
||||
titleLabel.set(text: newValue ?? "")
|
||||
setAccessibilityString(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Override this to conveniently get/set the textfield(s).
|
||||
public var text: String? {
|
||||
get { nil }
|
||||
@ -119,7 +121,7 @@ import UIKit
|
||||
public var feedback: String? {
|
||||
get { feedbackLabel.text }
|
||||
set {
|
||||
feedbackLabel.text = newValue
|
||||
feedbackLabel.set(text: newValue ?? "")
|
||||
feedbackLabel.accessibilityElementsHidden = feedbackLabel.text?.isEmpty ?? true
|
||||
}
|
||||
}
|
||||
@ -128,6 +130,9 @@ import UIKit
|
||||
model as? EntryFieldModel
|
||||
}
|
||||
|
||||
///This is the value of the entryFieldModel.text on set
|
||||
///This is used to update the text value on a reset or when enabled is set to false
|
||||
private var defaultText: String = ""
|
||||
//--------------------------------------------------
|
||||
// MARK: - Constraints
|
||||
//--------------------------------------------------
|
||||
@ -299,10 +304,17 @@ import UIKit
|
||||
super.set(with: model, delegateObject, additionalData)
|
||||
self.delegateObject = delegateObject
|
||||
|
||||
guard let model = model as? EntryFieldModel else { return }
|
||||
guard let model = entryFieldModel else { return }
|
||||
|
||||
entryFieldContainer.set(with: model, delegateObject, additionalData)
|
||||
|
||||
//setup the properties for setting models
|
||||
//later with enabled/showError
|
||||
titleLabel.setup(model: model.titleStateLabel, delegateObject, additionalData)
|
||||
feedbackLabel.setup(model: model.feedbackStateLabel, delegateObject, additionalData)
|
||||
|
||||
isEnabled = model.enabled
|
||||
defaultText = model.text ?? ""
|
||||
model.updateUI = { [weak self] in
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let self = self else { return }
|
||||
@ -313,6 +325,7 @@ import UIKit
|
||||
} else if model.isValid ?? true && self.showError {
|
||||
self.showError = false
|
||||
}
|
||||
self.isEnabled = model.enabled
|
||||
})
|
||||
}
|
||||
|
||||
@ -328,13 +341,10 @@ import UIKit
|
||||
self.updateValidation(validState)
|
||||
})
|
||||
}
|
||||
|
||||
title = model.title
|
||||
feedback = model.feedback
|
||||
isEnabled = model.enabled
|
||||
|
||||
entryFieldContainer.disableAllBorders = model.hideBorders
|
||||
accessibilityIdentifier = model.accessibilityIdentifier ?? model.fieldKey
|
||||
|
||||
|
||||
if let isLocked = model.locked {
|
||||
self.isLocked = isLocked
|
||||
|
||||
@ -364,3 +374,11 @@ extension EntryField {
|
||||
// To Be Overriden
|
||||
}
|
||||
}
|
||||
|
||||
extension LabelModel {
|
||||
public convenience init(fontStyle: Styler.Font, textColor: Color) {
|
||||
self.init(text: "")
|
||||
self.fontStyle = fontStyle
|
||||
self.textColor = textColor
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,8 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, EnableableModelProtocol {
|
||||
@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -18,8 +19,6 @@ import Foundation
|
||||
|
||||
public var backgroundColor: Color?
|
||||
public var accessibilityIdentifier: String?
|
||||
public var title: String?
|
||||
public var feedback: String?
|
||||
public var shouldClearText: Bool = false
|
||||
public var dynamicErrorMessage: String? {
|
||||
didSet {
|
||||
@ -40,6 +39,20 @@ import Foundation
|
||||
public var baseValue: AnyHashable?
|
||||
public var wasInitiallySelected: Bool = false
|
||||
|
||||
//text only
|
||||
//Used for re-encoding what was decoded
|
||||
private var title: String?
|
||||
private var feedback: String?
|
||||
|
||||
//label models
|
||||
//Used for re-encoding what was decoded
|
||||
private var titleLabel: LabelModel?
|
||||
private var feedbackLabel: LabelModel?
|
||||
|
||||
//used to drive the EntryFieldView UI
|
||||
public var titleStateLabel: StateLabelModel
|
||||
public var feedbackStateLabel: StateLabelModel
|
||||
|
||||
public var isValid: Bool? = true {
|
||||
didSet { updateUI?() }
|
||||
}
|
||||
@ -70,6 +83,8 @@ import Foundation
|
||||
case text
|
||||
case fieldKey
|
||||
case groupName
|
||||
case titleLabel
|
||||
case feedbackLabel
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -77,6 +92,8 @@ import Foundation
|
||||
//--------------------------------------------------
|
||||
|
||||
public func formFieldValue() -> AnyHashable? {
|
||||
guard enabled else { return nil }
|
||||
|
||||
if dynamicErrorMessage != nil {
|
||||
dynamicErrorMessage = nil
|
||||
}
|
||||
@ -92,7 +109,7 @@ import Foundation
|
||||
self.isValid = valid
|
||||
updateUI?()
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
@ -100,6 +117,8 @@ import Foundation
|
||||
public init(with text: String) {
|
||||
self.text = text
|
||||
baseValue = text
|
||||
self.titleStateLabel = StateLabelModel(text: "")
|
||||
self.feedbackStateLabel = StateLabelModel(text: "")
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -121,10 +140,24 @@ import Foundation
|
||||
hideBorders = try typeContainer.decodeIfPresent(Bool.self, forKey: .hideBorders) ?? false
|
||||
baseValue = text
|
||||
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
|
||||
|
||||
titleLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .titleLabel)
|
||||
feedbackLabel = try typeContainer.decodeIfPresent(LabelModel.self, forKey: .feedbackLabel)
|
||||
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
|
||||
self.groupName = groupName
|
||||
}
|
||||
|
||||
//Setup the stateLabelModels
|
||||
if let titleLabel = titleLabel {
|
||||
self.titleStateLabel = StateLabelModel(model: titleLabel)
|
||||
} else {
|
||||
self.titleStateLabel = StateLabelModel(text: title ?? "")
|
||||
}
|
||||
|
||||
if let feedBackLabel = feedbackLabel {
|
||||
self.feedbackStateLabel = StateLabelModel(model: feedBackLabel)
|
||||
} else { //feedback is the model for the error
|
||||
self.feedbackStateLabel = StateLabelModel(text: feedback ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -133,7 +166,9 @@ import Foundation
|
||||
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
|
||||
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
|
||||
try container.encodeIfPresent(title, forKey: .title)
|
||||
try container.encodeIfPresent(titleLabel, forKey: .titleLabel)
|
||||
try container.encodeIfPresent(feedback, forKey: .feedback)
|
||||
try container.encodeIfPresent(feedbackLabel, forKey: .feedbackLabel)
|
||||
try container.encodeIfPresent(text, forKey: .text)
|
||||
try container.encodeIfPresent(locked, forKey: .locked)
|
||||
try container.encodeIfPresent(showError, forKey: .showError)
|
||||
@ -142,6 +177,7 @@ import Foundation
|
||||
try container.encodeIfPresent(errorMessage, forKey: .errorMessage)
|
||||
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
|
||||
try container.encodeIfPresent(groupName, forKey: .groupName)
|
||||
|
||||
try container.encode(enabled, forKey: .enabled)
|
||||
try container.encode(hideBorders, forKey: .hideBorders)
|
||||
}
|
||||
|
||||
@ -423,6 +423,13 @@ import MVMCore
|
||||
checkAndBypassAnimations(selected: model.selected)
|
||||
}
|
||||
|
||||
model.updateUI = { [weak self] in
|
||||
MVMCoreDispatchUtility.performBlock(onMainThread: {
|
||||
guard let self = self else { return }
|
||||
self.isEnabled = model.enabled
|
||||
})
|
||||
}
|
||||
|
||||
isEnabled = model.enabled
|
||||
|
||||
if (model.action != nil || model.offAction != nil) {
|
||||
|
||||
@ -13,7 +13,8 @@
|
||||
var selected: Bool { get set }
|
||||
}
|
||||
|
||||
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol {
|
||||
@objcMembers public class CheckboxModel: MoleculeModelProtocol, SelectableMoleculeModelProtocol, FormFieldProtocol, UIUpdatableModelProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -22,7 +23,7 @@
|
||||
public var backgroundColor: Color?
|
||||
public var accessibilityIdentifier: String?
|
||||
public var selected: Bool = false
|
||||
public var enabled: Bool = true
|
||||
public var enabled: Bool = true
|
||||
public var animated: Bool = true
|
||||
public var inverted: Bool = false
|
||||
public var round: Bool = false
|
||||
@ -42,6 +43,7 @@
|
||||
public var fieldKey: String?
|
||||
public var groupName: String = FormValidator.defaultGroupName
|
||||
public var baseValue: AnyHashable?
|
||||
public var updateUI: ActionBlock?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
@ -75,7 +77,10 @@
|
||||
// MARK: - Form Validation
|
||||
//--------------------------------------------------
|
||||
|
||||
public func formFieldValue() -> AnyHashable? { selected }
|
||||
public func formFieldValue() -> AnyHashable? {
|
||||
guard enabled else { return nil }
|
||||
return selected
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
@objcMembers public class RadioBoxModel: MoleculeModelProtocol {
|
||||
@objcMembers public class RadioBoxModel: MoleculeModelProtocol, EnableableModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
public var fieldKey: String?
|
||||
public var groupName: String = FormValidator.defaultGroupName
|
||||
public var baseValue: AnyHashable?
|
||||
public var enabled: Bool = true
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
@ -39,6 +40,7 @@
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case moleculeName
|
||||
case enabled
|
||||
case selectedAccentColor
|
||||
case backgroundColor
|
||||
case accessibilityIdentifier
|
||||
@ -63,6 +65,9 @@
|
||||
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
|
||||
self.groupName = groupName
|
||||
}
|
||||
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
|
||||
self.enabled = enabled
|
||||
}
|
||||
baseValue = formFieldValue()
|
||||
}
|
||||
|
||||
|
||||
@ -17,7 +17,8 @@
|
||||
private var selectedRadioButton: RadioButton?
|
||||
private var selectedRadioButtonModel: RadioButtonModel?
|
||||
public var baseValue: AnyHashable?
|
||||
|
||||
public var enabled: Bool = true
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
@ -39,6 +40,7 @@
|
||||
} else {
|
||||
radioButton.isSelected = false
|
||||
}
|
||||
self.enabled = radioButtonModel.enabled
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
@objcMembers public class RadioSwatchModel: MoleculeModelProtocol {
|
||||
@objcMembers public class RadioSwatchModel: MoleculeModelProtocol, EnableableModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
public var fieldKey: String?
|
||||
public var groupName: String = FormValidator.defaultGroupName
|
||||
public var baseValue: AnyHashable?
|
||||
|
||||
public var enabled: Bool = true
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Methods
|
||||
//--------------------------------------------------
|
||||
@ -43,6 +44,7 @@
|
||||
case swatches
|
||||
case fieldKey
|
||||
case groupName
|
||||
case enabled
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -58,6 +60,9 @@
|
||||
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
|
||||
self.groupName = groupName
|
||||
}
|
||||
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
|
||||
self.enabled = enabled
|
||||
}
|
||||
baseValue = formFieldValue()
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
|
||||
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol, EnableableModelProtocol {
|
||||
public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
|
||||
|
||||
open class ArrowModel: MoleculeModelProtocol {
|
||||
open class ArrowModel: MoleculeModelProtocol, EnableableModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol {
|
||||
open class CarouselIndicatorModel: CarouselPagingModelProtocol, MoleculeModelProtocol, EnableableModelProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
@ -238,7 +238,7 @@ public typealias ActionBlock = () -> ()
|
||||
case left
|
||||
}
|
||||
|
||||
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
|
||||
public func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject? = nil, _ additionalData: [AnyHashable: Any]? = nil) {
|
||||
|
||||
clauses = []
|
||||
text = nil
|
||||
@ -584,9 +584,8 @@ public typealias ActionBlock = () -> ()
|
||||
|
||||
/// Will remove the values contained in attributedText.
|
||||
func clearAttributes() {
|
||||
|
||||
guard let labelText = text,
|
||||
let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count))
|
||||
guard let labelText = text, !labelText.isEmpty else { return }
|
||||
guard let attributes = attributedText?.attributes(at: 0, longestEffectiveRange: nil, in: NSRange(location: 0, length: labelText.count))
|
||||
else { return }
|
||||
|
||||
let attributedString = NSMutableAttributedString(string: labelText)
|
||||
|
||||
70
MVMCoreUI/Atomic/Atoms/Views/Label/StateLabel.swift
Normal file
70
MVMCoreUI/Atomic/Atoms/Views/Label/StateLabel.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// StateLabel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/10/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Subclass of label that helps with different states
|
||||
public class StateLabel: Label {
|
||||
//properties used in setting label
|
||||
private var delegateObject: MVMCoreUIDelegateObject?
|
||||
private var additionalData: [AnyHashable: Any]?
|
||||
|
||||
//models that drive the label UI
|
||||
private var stateModel: StateLabelModel!
|
||||
|
||||
//public properties
|
||||
public override var isEnabled: Bool {
|
||||
didSet{
|
||||
self.state = isEnabled ? .enabled : .disabled
|
||||
}
|
||||
}
|
||||
|
||||
public override func reset(){
|
||||
super.reset()
|
||||
self.state = .enabled
|
||||
}
|
||||
|
||||
//current mode of label
|
||||
public var state: StateLabelModel.State {
|
||||
get {
|
||||
return self.stateModel.state
|
||||
}
|
||||
set {
|
||||
self.stateModel.state = newValue
|
||||
self.set(with: stateModel.model, delegateObject, additionalData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in setting up the Label for use
|
||||
/// - Parameters:
|
||||
/// - text: If there is no Model, there should be a TEXT only version of the label that will be applied to the default font / color styles
|
||||
/// - model: Model takes priority over a text value. The model has its own text value that will be looked at to draw the screen, this model is used for both enabled/disabled models
|
||||
/// - delegateObject: passed in from the creator
|
||||
/// - additionalData: passed in from the creator
|
||||
public func setup(model: StateLabelModel, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){
|
||||
self.additionalData = additionalData
|
||||
self.delegateObject = delegateObject
|
||||
self.stateModel = model
|
||||
|
||||
//default to enabled state
|
||||
self.reset()
|
||||
}
|
||||
|
||||
/// Use this to switch the label into a error state
|
||||
/// - Parameter message: message to show in the errorModel
|
||||
public func showError(message: String){
|
||||
self.stateModel.set(text: message, for: .error)
|
||||
self.state = .error
|
||||
}
|
||||
|
||||
/// Text change that will update both enabledModel and disabledModel text values
|
||||
/// - Parameter text: text you want to see
|
||||
public func set(text: String?){
|
||||
self.stateModel.set(text: text ?? "", for: .enabled)
|
||||
}
|
||||
}
|
||||
91
MVMCoreUI/Atomic/Atoms/Views/Label/StateLabelModel.swift
Normal file
91
MVMCoreUI/Atomic/Atoms/Views/Label/StateLabelModel.swift
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// StateLabelModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/14/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class StateLabelModel {
|
||||
static let defaultFontStyle = Styler.Font.RegularMicro
|
||||
static let defaultEnabledTextColor = Color(uiColor: .mvmBlack)
|
||||
static let defaultDisabledTextColor = Color(uiColor: .mvmCoolGray3)
|
||||
static let defaultErrorTextColor = Color(uiColor: .mvmBlack)
|
||||
|
||||
private var enabledModel: LabelModel
|
||||
private var disabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultDisabledTextColor)
|
||||
private var errorLabelModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultErrorTextColor)
|
||||
|
||||
public enum State {
|
||||
case enabled
|
||||
case disabled
|
||||
case error
|
||||
}
|
||||
|
||||
//current state
|
||||
public var state: State = .enabled
|
||||
|
||||
//model is based on current state
|
||||
public var model: LabelModel {
|
||||
switch state {
|
||||
case .enabled:
|
||||
return enabledModel
|
||||
case .disabled:
|
||||
return disabledModel
|
||||
case .error:
|
||||
return errorLabelModel
|
||||
}
|
||||
}
|
||||
|
||||
//init
|
||||
public init(model: LabelModel){
|
||||
|
||||
//ensure the fontStyle exist and if so,
|
||||
//make sure the disabledModel font matches
|
||||
//otherwise set it to defaultFontStyle
|
||||
if let modelFontStyle = model.fontStyle {
|
||||
self.disabledModel.fontStyle = modelFontStyle
|
||||
} else {
|
||||
model.fontStyle = StateLabelModel.defaultFontStyle
|
||||
}
|
||||
|
||||
//ensure the textColor is set
|
||||
//otherwise use the defaultEnabledTextColor
|
||||
if model.textColor == nil {
|
||||
model.textColor = StateLabelModel.defaultEnabledTextColor
|
||||
}
|
||||
|
||||
//set the enabledModel to the model passed in
|
||||
self.enabledModel = model
|
||||
|
||||
//make sure the enabled & disabled text match
|
||||
self.disabledModel.text = self.enabledModel.text
|
||||
}
|
||||
|
||||
public init(text: String){
|
||||
//create the enabled model
|
||||
self.enabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultEnabledTextColor)
|
||||
self.enabledModel.text = text
|
||||
|
||||
//make sure the enabled & disabled text match
|
||||
self.disabledModel.text = self.enabledModel.text
|
||||
}
|
||||
|
||||
//methods
|
||||
public func reset(){
|
||||
self.state = .enabled
|
||||
}
|
||||
|
||||
//set text for state
|
||||
public func set(text:String, for state: State){
|
||||
switch state {
|
||||
case .enabled, .disabled:
|
||||
self.enabledModel.text = text
|
||||
self.disabledModel.text = text
|
||||
case .error:
|
||||
self.errorLabelModel.text = text
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,7 +37,8 @@ import UIKit
|
||||
public var baseValue: AnyHashable?
|
||||
public var fieldKey: String?
|
||||
public var groupName: String = FormValidator.defaultGroupName
|
||||
|
||||
public var enabled: Bool = true
|
||||
|
||||
public var selectable = false
|
||||
public var selectedIndex: Int?
|
||||
|
||||
|
||||
@ -9,6 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
|
||||
public protocol EnableableModelProtocol {
|
||||
public protocol EnableableModelProtocol: AnyObject {
|
||||
var enabled: Bool { get set }
|
||||
}
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
//
|
||||
// DisableFormFieldEffectModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/6/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class DisableFormFieldEffectModel: FormFieldEffectProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "disableFormFieldEffect"
|
||||
public var fieldKey: String = ""
|
||||
public var activatedRuleIds: [String]?
|
||||
public var rules: [RulesProtocol]
|
||||
|
||||
init(_ fieldKey: String, _ activatedRuleIds: [String], _ rules: [RulesProtocol]) {
|
||||
self.fieldKey = fieldKey
|
||||
self.activatedRuleIds = activatedRuleIds
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case fieldKey
|
||||
case activatedRuleIds
|
||||
case rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey)
|
||||
self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds)
|
||||
self.rules = try typeContainer.decodeModels(codingKey: .rules)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(fieldKey, forKey: .fieldKey)
|
||||
try container.encode(activatedRuleIds, forKey: .activatedRuleIds)
|
||||
try container.encodeModels(rules, forKey: .rules)
|
||||
}
|
||||
|
||||
public func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule) {
|
||||
field.enabled = !validity
|
||||
if let updateField = field as? UIUpdatableModelProtocol {
|
||||
updateField.updateUI?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
//
|
||||
// EnableFormFieldEffectModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/1/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public class EnableFormFieldEffectModel: FormFieldEffectProtocol {
|
||||
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "enableFormFieldEffect"
|
||||
public var fieldKey: String = ""
|
||||
public var activatedRuleIds: [String]?
|
||||
public var rules: [RulesProtocol]
|
||||
|
||||
init(_ fieldKey: String, activatedRuleIds: [String], rules: [RulesProtocol]) {
|
||||
self.fieldKey = fieldKey
|
||||
self.activatedRuleIds = activatedRuleIds
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case fieldKey
|
||||
case activatedRuleIds
|
||||
case rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey)
|
||||
self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds)
|
||||
self.rules = try typeContainer.decodeModels(codingKey: .rules)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(fieldKey, forKey: .fieldKey)
|
||||
try container.encode(activatedRuleIds, forKey: .activatedRuleIds)
|
||||
try container.encodeModels(rules, forKey: .rules)
|
||||
}
|
||||
|
||||
public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) {
|
||||
field.enabled = validity
|
||||
if let updateField = field as? UIUpdatableModelProtocol {
|
||||
updateField.updateUI?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
//
|
||||
// FormFieldEffectProtocol.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/6/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol FormFieldEffectProtocol: ModelProtocol, RulesContainerProtocol {
|
||||
|
||||
/// This is the key to the form field that will take the effect
|
||||
var fieldKey: String { get }
|
||||
|
||||
/// This is an array of ruleIds that are a property of RulesProtocol for the array of Validation rules within the FormGroupRule.rules
|
||||
/// When this effect "rules" are valid (true), the Form will add the fieldKey to the corresponding Validation rule "fields" property.
|
||||
/// When this effect "rules" are invalid (false), the Form will remove the fieldKey from the corresponding Validation rule "fields" property.
|
||||
var activatedRuleIds: [String]? { get }
|
||||
|
||||
/// This function will execute the effect on the field passed in based on the validity. validity is the result of the array of rules run.
|
||||
/// - Parameters:
|
||||
/// - validity: This is run against the FormFieldAffect
|
||||
/// - field: Form Field that is bound to the fieldKey
|
||||
/// - form: Form that the formGroup is attached
|
||||
/// - group: FormGroupRule that is attached to this effect
|
||||
func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule)
|
||||
}
|
||||
|
||||
public extension FormFieldEffectProtocol {
|
||||
var type: String { Self.identifier }
|
||||
static var categoryCodingKey: String { "type" }
|
||||
static var categoryName: String { "\(FormFieldEffectProtocol.self)" }
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
//
|
||||
// HideFormFieldEffectModel.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/6/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class HideFormFieldEffectModel: FormFieldEffectProtocol {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
public static var identifier: String = "hideFormFieldEffect"
|
||||
public var fieldKey: String = ""
|
||||
public var activatedRuleIds: [String]?
|
||||
public var rules: [RulesProtocol]
|
||||
|
||||
init(_ fieldKey: String, _ activatedRuleIds: [String], _ rules: [RulesProtocol]) {
|
||||
self.fieldKey = fieldKey
|
||||
self.activatedRuleIds = activatedRuleIds
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Keys
|
||||
//--------------------------------------------------
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case fieldKey
|
||||
case activatedRuleIds
|
||||
case rules
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Codec
|
||||
//--------------------------------------------------
|
||||
|
||||
required public init(from decoder: Decoder) throws {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.fieldKey = try typeContainer.decode(String.self, forKey: .fieldKey)
|
||||
self.activatedRuleIds = try typeContainer.decodeIfPresent([String].self, forKey: .activatedRuleIds)
|
||||
self.rules = try typeContainer.decodeModels(codingKey: .rules)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(fieldKey, forKey: .fieldKey)
|
||||
try container.encode(activatedRuleIds, forKey: .activatedRuleIds)
|
||||
try container.encodeModels(rules, forKey: .rules)
|
||||
}
|
||||
|
||||
public func setEffect(validity: Bool, field: FormFieldProtocol, form: FormValidator, group: FormGroupRule) {
|
||||
field.enabled = validity
|
||||
if let updateField = field as? UIUpdatableModelProtocol {
|
||||
updateField.updateUI?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,11 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol FormGroupWatcherFieldProtocol: FormItemProtocol {
|
||||
func setValidity(_ valid: Bool, group: FormGroupRule)
|
||||
public protocol FormGroupWatcherFieldProtocol: FormItemProtocol, UIUpdatableModelProtocol {}
|
||||
|
||||
public extension FormGroupWatcherFieldProtocol {
|
||||
func setValidity(_ valid: Bool){
|
||||
self.enabled = valid;
|
||||
updateUI?()
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol FormItemProtocol {
|
||||
public protocol FormItemProtocol: EnableableModelProtocol {
|
||||
/// The group name to used to group this item for validation, enableability, and value getting.
|
||||
var groupName: String { get set }
|
||||
}
|
||||
|
||||
@ -11,6 +11,11 @@ import MVMCore
|
||||
|
||||
|
||||
@objcMembers public class FormValidator: NSObject {
|
||||
|
||||
public enum ValidationError: Error {
|
||||
case other(error: String)
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
@ -80,38 +85,115 @@ import MVMCore
|
||||
|
||||
/// Validates all rule groups. Returns if valid
|
||||
public func validate() -> Bool {
|
||||
|
||||
var valid = true
|
||||
guard let formRules = formRules else { return valid }
|
||||
|
||||
for group in formRules {
|
||||
let groupValid = validateGroup(group)
|
||||
valid = valid && groupValid
|
||||
|
||||
var groupValid = false
|
||||
do {
|
||||
groupValid = try validateGroup(group)
|
||||
} catch {
|
||||
if let err = MVMCoreErrorObject.createErrorObject(for: error, location: "FormValidator"){
|
||||
MVMCoreLoggingHandler.shared()?.addError(toLog: err)
|
||||
fatalError(err.description)
|
||||
}
|
||||
}
|
||||
valid = valid && groupValid
|
||||
}
|
||||
|
||||
return valid
|
||||
}
|
||||
|
||||
/// Validates a given rule group. Returns if valid
|
||||
public func validateGroup(_ group: FormGroupRule) -> Bool {
|
||||
// Validate each rule.
|
||||
var valid = true
|
||||
var previousValidity: [String: Bool] = [:]
|
||||
for rule in group.rules {
|
||||
let tuple = rule.validate(fields, previousValidity)
|
||||
let isValidRule = tuple.valid
|
||||
let returnedValidity = tuple.fieldValidity
|
||||
previousValidity = previousValidity.merging(returnedValidity) { (_, new) in new }
|
||||
valid = valid && isValidRule
|
||||
}
|
||||
|
||||
/// Validates a FormGroupRule
|
||||
/// - Parameters:
|
||||
/// - group: FormGroupRule being validated
|
||||
/// - counter: keeps track of how many times causes another group validation
|
||||
/// - Returns: validity for the FormGroupRule.rules
|
||||
public func validateGroup(_ group: FormGroupRule, counter: Int = 0) throws -> Bool {
|
||||
let valid = group.validate(fields)
|
||||
|
||||
// Notify the group watchers of validity.
|
||||
for watcher in groupWatchers {
|
||||
if watcher.groupName == group.groupName {
|
||||
watcher.setValidity(valid, group: group)
|
||||
for watcher in groupWatchers.filter({$0.groupName == group.groupName}) {
|
||||
watcher.setValidity(valid)
|
||||
}
|
||||
|
||||
var ruleChange = false
|
||||
|
||||
//loop the effects
|
||||
group.effects?.forEach({ effect in
|
||||
//get the fieldKey for the effect
|
||||
if let effected = fields[effect.fieldKey] {
|
||||
//get the validity
|
||||
let validity = effect.validate(fields)
|
||||
|
||||
//set the effect with the validation
|
||||
effect.setEffect(validity: validity, field: effected, form: self, group: group)
|
||||
|
||||
//update the group form rules
|
||||
if let ruleIds = effect.activatedRuleIds {
|
||||
let didChange = self.updateRules(for: group, with: validity, for: effect.fieldKey, and: ruleIds)
|
||||
if(didChange) {
|
||||
ruleChange = didChange
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if ruleChange {
|
||||
if counter > 3 {
|
||||
throw ValidationError.other(error: "Effect caused validation loop error")
|
||||
} else {
|
||||
return try self.validateGroup(group, counter: counter + 1)
|
||||
}
|
||||
} else {
|
||||
return valid
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Updates the Fields in which a specific rule within a FormGroupRule validates against.
|
||||
/// - Parameters:
|
||||
/// - group: FormGroupRule to update
|
||||
/// - validity: If you the fieldKey should be added or removed from a rule
|
||||
/// - fieldKey: FieldKey that will be added or removed from a rule
|
||||
/// - ruleIds: Array of ruleIds to add or remove the fieldKey into the rule.fields property
|
||||
public func updateRules(for group: FormGroupRule, with validity: Bool, for fieldKey: String, and ruleIds: [String]) -> Bool{
|
||||
//update the group rules based on the validation of this rule to show/hide
|
||||
var ruleChange = false
|
||||
ruleIds.forEach { ruleId in
|
||||
//only change a rule found
|
||||
if var mutableRule = group.rules.first(where: { rule in return rule.ruleId == ruleId}) {
|
||||
//make a copy of the fields
|
||||
var mutableFields = mutableRule.fields
|
||||
//if the validity is true
|
||||
if validity {
|
||||
//we need to ensure this fieldKey is included in the found rule
|
||||
//therefore we mush append the fieldKey to the fields array
|
||||
if !mutableRule.fields.contains(where: { field in return field == fieldKey }){
|
||||
mutableFields.append(fieldKey)
|
||||
mutableRule.fields = mutableFields
|
||||
//a rule was changed, therefore we need
|
||||
//to re-run the validation on the group
|
||||
ruleChange = true
|
||||
}
|
||||
} else {
|
||||
//the validity is false, therefore we must ensure
|
||||
//that the fieldKey does NOT exist in the found
|
||||
//rule fields
|
||||
if let fieldKeyIndex = mutableFields.firstIndex(of: fieldKey) {
|
||||
mutableFields.remove(at: fieldKeyIndex)
|
||||
mutableRule.fields = mutableFields
|
||||
//a rule was changed, therefore we need
|
||||
//to re-run the validation on the group
|
||||
ruleChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return valid
|
||||
|
||||
//if any rules updated, run the validateGroup again
|
||||
return ruleChange
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,22 +9,22 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
open class FormGroupRule: Codable {
|
||||
open class FormGroupRule: RulesContainerProtocol, Codable {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
var groupName: String
|
||||
var rules: [RulesProtocol]
|
||||
|
||||
public var groupName: String
|
||||
public var rules: [RulesProtocol]
|
||||
public var effects: [FormFieldEffectProtocol]?;
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializer
|
||||
//--------------------------------------------------
|
||||
|
||||
init(_ groupName: String, _ rules: [RulesProtocol]) {
|
||||
public init(_ groupName: String, _ rules: [RulesProtocol], _ effects: [FormFieldEffectProtocol]) {
|
||||
self.groupName = groupName
|
||||
self.rules = rules
|
||||
self.effects = effects
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -34,6 +34,7 @@ open class FormGroupRule: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case groupName
|
||||
case rules
|
||||
case effects
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
@ -44,6 +45,7 @@ open class FormGroupRule: Codable {
|
||||
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.groupName = try typeContainer.decode(String.self, forKey: .groupName)
|
||||
self.rules = try typeContainer.decodeModels(codingKey: .rules)
|
||||
self.effects = try typeContainer.decodeModelsIfPresent(codingKey: .effects)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
||||
@ -15,6 +15,7 @@ public class RuleAllValueChangedModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "allValueChanged"
|
||||
public var type: String = RuleAllValueChangedModel.identifier
|
||||
public var ruleId: String?
|
||||
public var errorMessage: [String: String]?
|
||||
public var fields: [String]
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ public class RuleAnyRequiredModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "anyRequired"
|
||||
public var type: String = RuleRequiredModel.identifier
|
||||
public var ruleId: String?
|
||||
public var fields: [String]
|
||||
public var errorMessage: [String: String]?
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ public class RuleAnyValueChangedModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "anyValueChanged"
|
||||
public var type: String = RuleAnyValueChangedModel.identifier
|
||||
public var ruleId: String?
|
||||
public var errorMessage: [String: String]?
|
||||
public var fields: [String]
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "equalsIgnoreCase"
|
||||
public var type: String = RuleEqualsIgnoreCaseModel.identifier
|
||||
public var ruleId: String?
|
||||
public var fields: [String]
|
||||
public var errorMessage: [String: String]?
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ public class RuleEqualsModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "equals"
|
||||
public var type: String = RuleEqualsModel.identifier
|
||||
public var ruleId: String?
|
||||
public var fields: [String]
|
||||
public var errorMessage: [String: String]?
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
// Copyright © 2020 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
|
||||
public class RuleRegexModel: RulesProtocol {
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
@ -14,6 +13,7 @@ public class RuleRegexModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "regex"
|
||||
public var type: String = RuleRegexModel.identifier
|
||||
public var ruleId: String?
|
||||
public var fields: [String]
|
||||
public var regex: String
|
||||
public var errorMessage: [String: String]?
|
||||
@ -30,4 +30,5 @@ public class RuleRegexModel: RulesProtocol {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ public class RuleRequiredModel: RulesProtocol {
|
||||
|
||||
public static var identifier: String = "allRequired"
|
||||
public var type: String = RuleRequiredModel.identifier
|
||||
public var ruleId: String?
|
||||
public var errorMessage: [String: String]?
|
||||
public var fields: [String]
|
||||
|
||||
|
||||
@ -16,7 +16,11 @@ public enum RulesCodingKey: String, CodingKey {
|
||||
public protocol RulesProtocol: ModelProtocol {
|
||||
// The type of rule
|
||||
var type: String { get }
|
||||
|
||||
|
||||
//id for the rule
|
||||
var ruleId: String? { get }
|
||||
|
||||
//error message to show for the rule
|
||||
var errorMessage: [String: String]? { get }
|
||||
|
||||
// The fields that this rule applies to.
|
||||
@ -56,3 +60,25 @@ public extension RulesProtocol {
|
||||
return (valid: valid, fieldValidity: previousValidity)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol RulesContainerProtocol{
|
||||
var rules: [RulesProtocol] { get }
|
||||
}
|
||||
|
||||
public extension RulesContainerProtocol {
|
||||
func validate(_ fields: [String: FormFieldProtocol]) -> Bool {
|
||||
// Validate each rule.
|
||||
var valid = true
|
||||
var previousValidity: [String: Bool] = [:]
|
||||
for rule in self.rules {
|
||||
|
||||
//validate the rule against the fields
|
||||
let tuple = rule.validate(fields, previousValidity)
|
||||
|
||||
//merge the fieldValidity
|
||||
previousValidity = previousValidity.merging(tuple.fieldValidity) { (_, new) in new }
|
||||
valid = valid && tuple.valid
|
||||
}
|
||||
return valid
|
||||
}
|
||||
}
|
||||
|
||||
13
MVMCoreUI/FormUIHelpers/UIUpdatableModelProtocol.swift
Normal file
13
MVMCoreUI/FormUIHelpers/UIUpdatableModelProtocol.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// Updatable.swift
|
||||
// MVMCoreUI
|
||||
//
|
||||
// Created by Matt Bruce on 12/1/21.
|
||||
// Copyright © 2021 Verizon Wireless. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol UIUpdatableModelProtocol {
|
||||
var updateUI: ActionBlock? { get set }
|
||||
}
|
||||
@ -243,5 +243,8 @@ open class CoreUIModelMapping: ModelMapping {
|
||||
ModelRegistry.register(RuleEqualsModel.self)
|
||||
ModelRegistry.register(RuleEqualsIgnoreCaseModel.self)
|
||||
ModelRegistry.register(RuleRegexModel.self)
|
||||
ModelRegistry.register(EnableFormFieldEffectModel.self)
|
||||
ModelRegistry.register(DisableFormFieldEffectModel.self)
|
||||
ModelRegistry.register(HideFormFieldEffectModel.self)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user