Merge branch 'feature/optional-readonly' into 'develop'

update to Forms

See merge request BPHV_MIPS/mvm_core_ui!791
This commit is contained in:
Hedden, Kyle Matthew 2022-01-13 19:08:08 +00:00
commit e990504ce3
36 changed files with 341 additions and 244 deletions

View File

@ -570,6 +570,8 @@
DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBC4391A224421A0001AB423 /* CaretLink.swift */; };
DBEFFA04225A829700230692 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB891E822253FA8500022516 /* Label.swift */; };
EA41F4AC2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */; };
EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */; };
EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA05EFAA278DE53600828819 /* ClearableModelProtocol.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 */; };
@ -577,8 +579,8 @@
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 */; };
EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC1402763BB8D00E78B40 /* FormLabel.swift */; };
EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFC151276913E800E78B40 /* FormLabelModel.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@ -1148,6 +1150,8 @@
DBC4391722442197001AB423 /* DashLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DashLine.swift; sourceTree = "<group>"; };
DBC4391A224421A0001AB423 /* CaretLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CaretLink.swift; sourceTree = "<group>"; };
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicRuleFormFieldEffectModel.swift; sourceTree = "<group>"; };
EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearFormFieldEffectModel.swift; sourceTree = "<group>"; };
EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearableModelProtocol.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>"; };
@ -1155,8 +1159,8 @@
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>"; };
EABFC1402763BB8D00E78B40 /* FormLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabel.swift; sourceTree = "<group>"; };
EABFC151276913E800E78B40 /* FormLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormLabelModel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1181,6 +1185,7 @@
D23EA7FA2475F09800D60C34 /* CarouselItemProtocol.swift */,
012A88C3238D86E600FE3DA1 /* CarouselItemModelProtocol.swift */,
012A88B0238C880100FE3DA1 /* CarouselPagingModelProtocol.swift */,
EA05EFAA278DE53600828819 /* ClearableModelProtocol.swift */,
01EB3683236097C0006832FA /* MoleculeModelProtocol.swift */,
012A889B23889E8400FE3DA1 /* TemplateModelProtocol.swift */,
D28A837823C7D5BC00DFE4FC /* PageModelProtocol.swift */,
@ -1446,8 +1451,8 @@
94C2D9A823872E5E0006CF46 /* LabelAttributeImageModel.swift */,
94C2D9AA23872EB50006CF46 /* LabelAttributeActionModel.swift */,
DB891E822253FA8500022516 /* Label.swift */,
EABFC151276913E800E78B40 /* StateLabelModel.swift */,
EABFC1402763BB8D00E78B40 /* StateLabel.swift */,
EABFC151276913E800E78B40 /* FormLabelModel.swift */,
EABFC1402763BB8D00E78B40 /* FormLabel.swift */,
);
path = Label;
sourceTree = "<group>";
@ -2428,6 +2433,7 @@
EA41F4AB2787927100F5B377 /* DynamicRuleFormFieldEffectModel.swift */,
EA7E67732758310500ABF773 /* EnableFormFieldEffectModel.swift */,
EAA0CFB0275E823A00D65EB0 /* HideFormFieldEffectModel.swift */,
EA05EFA8278DDE2C00828819 /* ClearFormFieldEffectModel.swift */,
);
path = FormFieldEffect;
sourceTree = "<group>";
@ -2570,7 +2576,7 @@
324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */,
5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */,
AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */,
EABFC152276913E800E78B40 /* StateLabelModel.swift in Sources */,
EABFC152276913E800E78B40 /* FormLabelModel.swift in Sources */,
0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */,
0AF60F0926B3316E00AC3DB4 /* MVMCoreUIUtility+Extension.swift in Sources */,
8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */,
@ -2851,6 +2857,7 @@
D29DF28321E7AB24003B2FB9 /* MVMCoreUICommonViewsUtility.m in Sources */,
011B58F223A2AE2C0085F53C /* DropDownListItemModel.swift in Sources */,
D2509ED12472ED9B001BFB9D /* NavigationItemModelProtocol.swift in Sources */,
EA05EFAB278DE53600828819 /* ClearableModelProtocol.swift in Sources */,
8D448E5524050A46006211BB /* ListOneColumnFullWidthTextAllTextAndLinksModel.swift in Sources */,
BBC0C4FD24811DBC0087C44F /* Tag.swift in Sources */,
94C2D9842386F3F80006CF46 /* LabelAttributeModel.swift in Sources */,
@ -2995,7 +3002,7 @@
013F801923FB4A8E00AD8013 /* UIContentMode+Extension.swift in Sources */,
AA104AC724472DB0004D2810 /* HeadersH1Button.swift in Sources */,
525239C22407BD1000454969 /* ListTwoColumnPriceDetails.swift in Sources */,
EABFC1412763BB8D00E78B40 /* StateLabel.swift in Sources */,
EABFC1412763BB8D00E78B40 /* FormLabel.swift in Sources */,
AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */,
D2A5146122121FBF00345BFB /* MoleculeStackTemplate.swift in Sources */,
D23EA7FB2475F09800D60C34 /* CarouselItemProtocol.swift in Sources */,
@ -3017,6 +3024,7 @@
52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */,
D2ED2812254B0EB800A1C293 /* MVMCoreTopAlertObject.m in Sources */,
0AA4D2E125CAEC72008DB32D /* AccessibilityModelProtocol.swift in Sources */,
EA05EFA9278DDE2C00828819 /* ClearFormFieldEffectModel.swift in Sources */,
C003506123AA94CD00B6AC29 /* Button.swift in Sources */,
DBC4391B224421A0001AB423 /* CaretLink.swift in Sources */,
D29C559025C095210082E7D6 /* Video.swift in Sources */,

View File

@ -44,7 +44,7 @@ import UIKit
super.isEnabled = enabled
}
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------

View File

@ -17,8 +17,8 @@ import UIKit
//--------------------------------------------------
// MARK: - Outlets
//--------------------------------------------------
public private(set) var titleLabel: StateLabel = {
let label = StateLabel()
public private(set) var titleLabel: FormLabel = {
let label = FormLabel()
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
@ -26,12 +26,33 @@ import UIKit
public private(set) var entryFieldContainer = EntryFieldContainer()
/// Provides contextual information on the TextField.
public private(set) var feedbackLabel: StateLabel = {
let label = StateLabel()
public private(set) var feedbackLabel: FormLabel = {
let label = FormLabel()
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
public private(set) var errorLabel: Label = {
let label = Label()
label.setFontStyle(.RegularMicro)
label.textColor = .mvmBlack
label.setContentCompressionResistancePriority(.required, for: .vertical)
return label
}()
public lazy var stack: UIStackView = {
errorLabel.isHidden = true
let stack = UIStackView(arrangedSubviews: [titleLabel, entryFieldContainer, errorLabel, feedbackLabel])
stack.axis = .vertical
stack.alignment = .fill
stack.distribution = .fill
stack.setCustomSpacing(Padding.One, after: titleLabel)
stack.setCustomSpacing(Padding.Two, after: entryFieldContainer)
stack.setCustomSpacing(Padding.One, after: errorLabel)
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
//--------------------------------------------------
// MARK: - Delegate
//--------------------------------------------------
@ -59,21 +80,26 @@ import UIKit
feedbackLabel.isEnabled = enabled
entryFieldContainer.isEnabled = enabled
entryFieldModel?.enabled = enabled
if !enabled {
self.text = defaultText
}
}
}
}
/// Toggles enabled (original) or disabled UI.
public var isRequired: Bool = true {
didSet{
titleLabel.isRequired = isRequired
}
}
/// Toggles error or original UI.
public var showError: Bool {
get { entryFieldContainer.showError }
set (error) {
if error {
feedbackLabel.showError(message: self.errorMessage ?? "")
errorLabel.text = self.errorMessage ?? ""
errorLabel.isHidden = false
} else {
feedbackLabel.reset()
errorLabel.isHidden = true
}
entryFieldContainer.showError = error
entryFieldModel?.showError = error
@ -130,25 +156,6 @@ 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
//--------------------------------------------------
public var entryFieldContainerLeading: NSLayoutConstraint?
public var entryFieldContainerTrailing: NSLayoutConstraint?
public var feedbackLabelTrailing: NSLayoutConstraint?
public var feedbackLabelLeading: NSLayoutConstraint?
public var titleLabelLeading: NSLayoutConstraint?
public var titleLabelTrailing: NSLayoutConstraint?
public var titleContainerDistance: NSLayoutConstraint?
public var feedbackContainerDistance: NSLayoutConstraint?
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
@ -188,37 +195,18 @@ import UIKit
isAccessibilityElement = false
setContentCompressionResistancePriority(.required, for: .vertical)
accessibilityElements = [titleLabel, feedbackLabel]
accessibilityElements = [titleLabel, errorLabel, feedbackLabel]
backgroundColor = .mvmWhite
addSubview(titleLabel)
titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
titleLabelLeading = titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
titleLabelLeading?.isActive = true
titleLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: titleLabel.trailingAnchor)
titleLabelLeading?.isActive = true
addSubview(entryFieldContainer)
addSubview(stack)
entryFieldContainer.setContentCompressionResistancePriority(.required, for: .vertical)
setupFieldContainerContent(entryFieldContainer)
titleContainerDistance = entryFieldContainer.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: Padding.One)
titleContainerDistance?.isActive = true
entryFieldContainerLeading = entryFieldContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
entryFieldContainerLeading?.isActive = true
entryFieldContainerTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: entryFieldContainer.trailingAnchor)
entryFieldContainerTrailing?.isActive = true
addSubview(feedbackLabel)
feedbackContainerDistance = feedbackLabel.topAnchor.constraint(equalTo: entryFieldContainer.bottomAnchor, constant: Padding.Two)
feedbackContainerDistance?.isActive = true
feedbackLabelLeading = feedbackLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor)
feedbackLabelLeading?.isActive = true
feedbackLabelTrailing = layoutMarginsGuide.trailingAnchor.constraint(equalTo: feedbackLabel.trailingAnchor)
feedbackLabelTrailing?.isActive = true
layoutMarginsGuide.bottomAnchor.constraint(equalTo: feedbackLabel.bottomAnchor).isActive = true
stack.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
layoutMarginsGuide.trailingAnchor.constraint(equalTo: stack.trailingAnchor).isActive = true
layoutMarginsGuide.bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true
}
@objc open override func layoutSubviews() {
@ -234,10 +222,7 @@ import UIKit
@objc open override func updateView(_ size: CGFloat) {
super.updateView(size)
titleLabel.updateView(size)
feedbackLabel.updateView(size)
entryFieldContainer.updateView(size)
stack.updateView(size)
}
//--------------------------------------------------
@ -295,6 +280,9 @@ import UIKit
titleLabel.textColor = .mvmBlack
feedbackLabel.font = Styler.Font.RegularMicro.getFont()
feedbackLabel.textColor = .mvmBlack
errorLabel.font = Styler.Font.RegularMicro.getFont()
errorLabel.textColor = .mvmBlack
errorLabel.text = nil
entryFieldContainer.disableAllBorders = false
feedbackLabel.text = nil
entryFieldContainer.reset()
@ -313,8 +301,8 @@ import UIKit
titleLabel.setup(model: model.titleStateLabel, delegateObject, additionalData)
feedbackLabel.setup(model: model.feedbackStateLabel, delegateObject, additionalData)
defaultText = model.text ?? ""
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
isRequired = model.required
model.updateUI = { [weak self] in
MVMCoreDispatchUtility.performBlock(onMainThread: {
guard let self = self else { return }
@ -326,6 +314,7 @@ import UIKit
self.showError = false
}
self.isEnabled = model.enabled
self.text = model.text
})
}
@ -376,8 +365,8 @@ extension EntryField {
}
extension LabelModel {
public convenience init(fontStyle: Styler.Font, textColor: Color) {
self.init(text: "")
public convenience init(text: String = "", fontStyle: Styler.Font, textColor: Color) {
self.init(text: text)
self.fontStyle = fontStyle
self.textColor = textColor
}

View File

@ -9,7 +9,7 @@
import Foundation
@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol {
@objcMembers open class EntryFieldModel: MoleculeModelProtocol, FormFieldProtocol, FormRuleWatcherFieldProtocol, UIUpdatableModelProtocol, ClearableModelProtocol {
//--------------------------------------------------
// MARK: - Properties
@ -29,6 +29,8 @@ import Foundation
public var errorMessage: String?
public var errorTextColor: Color?
public var enabled: Bool = true
public var required: Bool = true
public var readOnly: Bool = false
public var showError: Bool?
public var hideBorders = false
public var locked: Bool?
@ -38,20 +40,12 @@ import Foundation
public var groupName: String = FormValidator.defaultGroupName
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?
public var title: String?
public var feedback: String?
//used to drive the EntryFieldView UI
public var titleStateLabel: StateLabelModel
public var feedbackStateLabel: StateLabelModel
public var titleStateLabel: FormLabelModel
public var feedbackStateLabel: FormLabelModel
public var isValid: Bool? = true {
didSet { updateUI?() }
@ -73,6 +67,7 @@ import Foundation
case accessibilityIdentifier
case title
case enabled
case readOnly
case feedback
case errorMessage
case errorTextColor
@ -83,8 +78,7 @@ import Foundation
case text
case fieldKey
case groupName
case titleLabel
case feedbackLabel
case required
}
//--------------------------------------------------
@ -117,8 +111,15 @@ import Foundation
public init(with text: String) {
self.text = text
baseValue = text
self.titleStateLabel = StateLabelModel(text: "")
self.feedbackStateLabel = StateLabelModel(text: "")
self.titleStateLabel = FormLabelModel(text: "")
self.feedbackStateLabel = FormLabelModel(text: "")
}
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
public func clear() {
self.text = ""
}
//--------------------------------------------------
@ -134,30 +135,21 @@ import Foundation
errorMessage = try typeContainer.decodeIfPresent(String.self, forKey: .errorMessage)
errorTextColor = try typeContainer.decodeIfPresent(Color.self, forKey: .errorTextColor)
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
locked = try typeContainer.decodeIfPresent(Bool.self, forKey: .locked)
selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected)
text = try typeContainer.decodeIfPresent(String.self, forKey: .text)
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 ?? "")
}
self.titleStateLabel = FormLabelModel(text: title ?? "")
self.feedbackStateLabel = FormLabelModel(model: LabelModel(text: feedback ?? "",
fontStyle: FormLabelModel.defaultFontStyle,
textColor: Color(uiColor: .mvmCoolGray6)))
}
public func encode(to encoder: Encoder) throws {
@ -166,9 +158,7 @@ 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)
@ -178,7 +168,9 @@ import Foundation
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(readOnly, forKey: .readOnly)
try container.encode(enabled, forKey: .enabled)
try container.encode(required, forKey: .required)
try container.encode(hideBorders, forKey: .hideBorders)
}
}

View File

@ -46,7 +46,7 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
if self.textView.isShowingPlaceholder {
self.textView.textColor = self.textView.placeholderTextColor
} else {
self.textView.textColor = (enabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor
self.textView.textColor = (self.textView.isEnabled ? self.textViewEntryFieldModel?.enabledTextColor : self.textViewEntryFieldModel?.disabledTextColor)?.uiColor
}
}
}
@ -282,8 +282,5 @@ class TextViewEntryField: EntryField, UITextViewDelegate, ObservingTextFieldDele
adjustMarginConstraints(constant: 0)
}
if !model.enabled {
isEnabled = false
}
}
}

View File

@ -95,7 +95,7 @@ import MVMCore
}
}
}
public var disabledBackgroundColor: UIColor = .clear
public var disabledBorderColor: UIColor = .mvmCoolGray3
public var disabledCheckColor: UIColor = .mvmCoolGray3
@ -430,7 +430,7 @@ import MVMCore
})
}
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
if (model.action != nil || model.offAction != nil) {
actionBlock = { [weak self] in

View File

@ -23,7 +23,8 @@
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 readOnly: Bool = false
public var animated: Bool = true
public var inverted: Bool = false
public var round: Bool = false
@ -54,6 +55,7 @@
case accessibilityIdentifier
case checked
case enabled
case readOnly
case inverted
case animated
case round
@ -158,10 +160,8 @@
self.inverted = inverted
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey)
@ -191,7 +191,8 @@
try container.encodeIfPresent(disabledCheckColor, forKey: .disabledCheckColor)
try container.encodeIfPresent(animated, forKey: .animated)
try container.encodeIfPresent(round, forKey: .round)
try container.encodeIfPresent(enabled, forKey: .enabled)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeModelIfPresent(offAction, forKey: .offAction)

View File

@ -62,7 +62,7 @@ import UIKit
heartPath.close()
heart.path = heartPath.cgPath
heart.fillColor = isSelected ? heartModel?.activeColor.cgColor : heartModel?.inActiveColor.cgColor
heart.opacity = 1.0
heart.opacity = isEnabled ? 1.0 : 0.5
heart.lineWidth = 1
heart.strokeColor = isSelected ? UIColor.clear.cgColor : UIColor.mvmBlack.cgColor
return heart
@ -88,7 +88,7 @@ import UIKit
self.additionalData = additionalData
guard let model = model as? HeartModel else { return }
isSelected = model.isActive
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
updateAccessibilityLabel()
}

View File

@ -21,8 +21,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
public var inActiveColor: Color = Color(uiColor: .clear)
public var action: ActionModelProtocol = ActionNoopModel()
public var enabled: Bool = true
//--------------------------------------------------
public var readOnly: Bool = false
// MARK: - Keys
//--------------------------------------------------
@ -35,6 +35,7 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
case inActiveColor
case action
case enabled
case readOnly
}
//--------------------------------------------------
@ -61,9 +62,8 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
if let action: ActionModelProtocol = try typeContainer.decodeModelIfPresent(codingKey: .action) {
self.action = action
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -76,5 +76,6 @@ open class HeartModel: MoleculeModelProtocol, EnableableModelProtocol {
try container.encode(inActiveColor, forKey: .inActiveColor)
try container.encodeModel(action, forKey: .action)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -39,7 +39,7 @@ open class RadioBox: Control, MFButtonProtocol {
public override var isEnabled: Bool {
didSet { updateAccessibility() }
}
//--------------------------------------------------
// MARK: - MVMCoreViewProtocol
//--------------------------------------------------
@ -89,7 +89,7 @@ open class RadioBox: Control, MFButtonProtocol {
accentColor = color
}
isSelected = model.selected
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
}
open override func reset() {

View File

@ -19,6 +19,7 @@
public var selectedAccentColor: Color?
public var selected: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
public var strikethrough: Bool = false
public var fieldValue: String?
public var action: ActionModelProtocol?
@ -39,6 +40,7 @@
case strikethrough
case fieldValue
case action
case readOnly
}
//--------------------------------------------------
@ -57,9 +59,8 @@
selected = isSelected
}
if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
enabled = isEnabled
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
strikethrough = isStrikeTrough
@ -79,6 +80,7 @@
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encode(selected, forKey: .selected)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
try container.encode(strikethrough, forKey: .strikethrough)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encodeModelIfPresent(action, forKey: .action)

View File

@ -21,7 +21,7 @@
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var enabled: Bool = true
public var readOnly: Bool = false
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@ -42,6 +42,7 @@
private enum CodingKeys: String, CodingKey {
case moleculeName
case enabled
case readOnly
case selectedAccentColor
case backgroundColor
case accessibilityIdentifier
@ -66,9 +67,8 @@
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
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
baseValue = formFieldValue()
}
@ -81,5 +81,7 @@
try container.encodeIfPresent(accessibilityIdentifier, forKey: .accessibilityIdentifier)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encode(groupName, forKey: .groupName)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -66,7 +66,7 @@ import UIKit
open override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let color = isEnabled ? enabledColor.cgColor : disabledColor.cgColor
let color = isEnabled == false ? disabledColor.cgColor : enabledColor.cgColor
layer.cornerRadius = bounds.width * 0.5
layer.borderColor = color
layer.borderWidth = bounds.width * 0.0333
@ -163,7 +163,7 @@ import UIKit
guard let model = model as? RadioButtonModel else { return }
isSelected = model.state
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
RadioButtonSelectionHelper.setupForRadioButtonGroup(model, self, delegateObject: delegateObject)
}

View File

@ -19,6 +19,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
public var accessibilityIdentifier: String?
public var state: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
/// The specific value to send to server. TODO: update this to be more generic.
public var fieldValue: String?
@ -42,6 +43,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
case fieldKey
case groupName
case action
case readOnly
}
//--------------------------------------------------
@ -72,11 +74,9 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) {
self.state = state
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
accessibilityIdentifier = try typeContainer.decodeIfPresent(String.self, forKey: .accessibilityIdentifier)
@ -96,6 +96,7 @@ open class RadioButtonModel: MoleculeModelProtocol, FormFieldProtocol {
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(state, forKey: .state)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)

View File

@ -18,7 +18,8 @@
private var selectedRadioButtonModel: RadioButtonModel?
public var baseValue: AnyHashable?
public var enabled: Bool = true
public var readOnly: Bool = false
//--------------------------------------------------
// MARK: - Initializer
//--------------------------------------------------
@ -41,6 +42,7 @@
radioButton.isSelected = false
}
self.enabled = radioButtonModel.enabled
self.readOnly = radioButtonModel.readOnly
}
//--------------------------------------------------

View File

@ -38,7 +38,7 @@ open class RadioSwatch: Control, MFButtonProtocol {
updateAccessibility()
}
}
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@ -64,7 +64,7 @@ open class RadioSwatch: Control, MFButtonProtocol {
self.additionalData = additionalData
bottomText.text = model.text
isSelected = model.selected
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
}
public override func reset() {

View File

@ -19,6 +19,7 @@
public var text: String?
public var selected: Bool = false
public var enabled: Bool = true
public var readOnly: Bool = false
public var strikethrough: Bool = false
public var fieldValue: String?
public var action: ActionModelProtocol?
@ -38,6 +39,7 @@
case strikethrough
case fieldValue
case action
case readOnly
}
//--------------------------------------------------
@ -58,17 +60,15 @@
if let selected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) {
self.selected = selected
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let strikethrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) {
self.strikethrough = strikethrough
}
fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue)
action = try typeContainer.decodeModelIfPresent(codingKey: .action)
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -83,5 +83,6 @@
try container.encode(strikethrough, forKey: .strikethrough)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encodeModelIfPresent(action, forKey: .action)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -20,7 +20,8 @@
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
public var enabled: Bool = true
public var readOnly: Bool = false
//--------------------------------------------------
// MARK: - Methods
//--------------------------------------------------
@ -46,6 +47,7 @@
case fieldKey
case groupName
case enabled
case readOnly
}
//--------------------------------------------------
@ -61,9 +63,8 @@
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
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
baseValue = formFieldValue()
}
@ -75,5 +76,7 @@
try container.encode(swatches, forKey: .swatches)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encode(groupName, forKey: .groupName)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -66,7 +66,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: isEnabled ? "AccToggleHint" : "AccDisabled")
}
}
/// Simple means to prevent user interaction with the toggle.
public var isLocked: Bool = false {
didSet { isUserInteractionEnabled = !isLocked }
@ -384,7 +384,7 @@ public typealias ActionBlockConfirmation = () -> (Bool)
isOn = model.selected
changeStateNoAnimation(isOn)
isAnimated = model.animated
isEnabled = model.enabled
isEnabled = model.enabled && !model.readOnly
if let accessibileString = model.accessibilityText {
accessibilityLabel = accessibileString

View File

@ -18,6 +18,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
public var selected: Bool = false
public var animated: Bool = true
public var enabled: Bool = true
public var readOnly: Bool = false
public var action: ActionModelProtocol?
public var alternateAction: ActionModelProtocol?
public var accessibilityText: String?
@ -39,6 +40,7 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
case state
case animated
case enabled
case readOnly
case action
case backgroundColor
case accessibilityIdentifier
@ -81,10 +83,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
self.selected = state
}
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let animated = try typeContainer.decodeIfPresent(Bool.self, forKey: .animated) {
self.animated = animated
}
@ -117,6 +115,8 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) {
self.groupName = groupName
}
enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false
}
public func encode(to encoder: Encoder) throws {
@ -136,5 +136,6 @@ public class ToggleModel: MoleculeModelProtocol, FormFieldProtocol {
try container.encodeIfPresent(accessibilityText, forKey: .accessibilityText)
try container.encodeIfPresent(fieldKey, forKey: .fieldKey)
try container.encodeIfPresent(groupName, forKey: .groupName)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -79,7 +79,7 @@ open class BarsIndicatorView: CarouselIndicator {
}
}
}
/// Colors the currently selected index, unique from other indicators
public var currentIndicatorColor: UIColor {
get { barsCarouselIndicatorModel?.currentIndicatorColor.uiColor ?? indicatorColor }

View File

@ -44,7 +44,7 @@ open class NumericIndicatorView: CarouselIndicator {
open override var isEnabled: Bool {
didSet { setViewColor(isEnabled ? indicatorColor : disabledIndicatorColor) }
}
/// Sets the color for pageCount text, left arrow and right arrow.
public override var indicatorColor: UIColor {
get { super.indicatorColor }

View File

@ -9,35 +9,31 @@
import Foundation
/// Subclass of label that helps with different states
public class StateLabel: Label {
public class FormLabel: 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!
private var formModel: FormLabelModel!
//public properties
public override var isEnabled: Bool {
didSet{
self.state = isEnabled ? .enabled : .disabled
self.formModel.enabled = isEnabled
self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData)
}
}
public var isRequired: Bool = true {
didSet{
self.set(with: isRequired ? formModel.model : formModel.requiredModel, delegateObject, additionalData)
}
}
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)
}
self.isEnabled = true
}
/// Used in setting up the Label for use
@ -46,25 +42,18 @@ public class StateLabel: Label {
/// - 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]?){
public func setup(model: FormLabelModel, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?){
self.additionalData = additionalData
self.delegateObject = delegateObject
self.stateModel = model
self.formModel = 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)
self.formModel.set(text: text ?? "")
}
}

View File

@ -8,37 +8,23 @@
import Foundation
public class StateLabelModel {
public class FormLabelModel: EnableableModelProtocol {
static let defaultFontStyle = Styler.Font.RegularMicro
static let defaultEnabledTextColor = Color(uiColor: .mvmBlack)
static let defaultDisabledTextColor = Color(uiColor: .mvmCoolGray3)
static let defaultErrorTextColor = Color(uiColor: .mvmBlack)
static let defaultRequiredTextColor = Color(uiColor: .mvmCoolGray6)
private var enabledModel: LabelModel
private var disabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultDisabledTextColor)
private var errorLabelModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultErrorTextColor)
private var disabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultDisabledTextColor)
public enum State {
case enabled
case disabled
case error
}
//current state
public var state: State = .enabled
public var enabled: Bool = true
//model is based on current state
public var model: LabelModel {
switch state {
case .enabled:
return enabledModel
case .disabled:
return disabledModel
case .error:
return errorLabelModel
}
return enabled ? enabledModel: disabledModel
}
//init
public init(model: LabelModel){
@ -48,13 +34,13 @@ public class StateLabelModel {
if let modelFontStyle = model.fontStyle {
self.disabledModel.fontStyle = modelFontStyle
} else {
model.fontStyle = StateLabelModel.defaultFontStyle
model.fontStyle = FormLabelModel.defaultFontStyle
}
//ensure the textColor is set
//otherwise use the defaultEnabledTextColor
if model.textColor == nil {
model.textColor = StateLabelModel.defaultEnabledTextColor
model.textColor = FormLabelModel.defaultEnabledTextColor
}
//set the enabledModel to the model passed in
@ -66,26 +52,30 @@ public class StateLabelModel {
public init(text: String){
//create the enabled model
self.enabledModel = LabelModel(fontStyle: StateLabelModel.defaultFontStyle, textColor: StateLabelModel.defaultEnabledTextColor)
self.enabledModel = LabelModel(fontStyle: FormLabelModel.defaultFontStyle, textColor: FormLabelModel.defaultEnabledTextColor)
self.enabledModel.text = text
//make sure the enabled & disabled text match
self.disabledModel.text = self.enabledModel.text
}
public var requiredModel: LabelModel {
let required = LabelModel(fontStyle: model.fontStyle!, textColor: model.textColor!)
if enabled {
required.attributes = [LabelAttributeColorModel(FormLabelModel.defaultRequiredTextColor, model.text.count + 1, 8)]
}
required.text = "\(model.text) Optional"
return required
}
//methods
public func reset(){
self.state = .enabled
self.enabled = true
}
//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
}
public func set(text:String){
self.enabledModel.text = text
self.disabledModel.text = text
}
}

View File

@ -24,10 +24,14 @@
case textColor
}
public init(_ textColor: Color?, _ location: Int, _ length: Int){
self.textColor = textColor
super.init(location, length)
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
textColor = try typeContainer.decodeIfPresent(Color.self, forKey: .textColor)

View File

@ -20,6 +20,7 @@
public var analyticsData: JSONValueDictionary?
public var fieldValue: String?
public var enabled: Bool = true
public var readOnly: Bool = false
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var baseValue: AnyHashable?
@ -40,6 +41,8 @@
case fieldValue
case fieldKey
case groupName
case enabled
case readOnly
}
//--------------------------------------------------
@ -57,6 +60,12 @@
self.groupName = groupName
}
baseValue = fieldValue
if let enabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) {
self.enabled = enabled
}
if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) {
self.readOnly = readOnly
}
try super.init(from: decoder)
}
@ -67,5 +76,7 @@
try container.encodeIfPresent(peakingArrowColor, forKey: .peakingArrowColor)
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
try container.encodeIfPresent(fieldValue, forKey: .fieldValue)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -38,7 +38,8 @@ import UIKit
public var fieldKey: String?
public var groupName: String = FormValidator.defaultGroupName
public var enabled: Bool = true
public var readOnly: Bool = false
public var selectable = false
public var selectedIndex: Int?
@ -87,6 +88,8 @@ import UIKit
case fieldKey
case selectable
case selectedIndex
case enabled
case readOnly
}
//--------------------------------------------------
@ -120,10 +123,16 @@ import UIKit
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
}
if let readOnly = try typeContainer.decodeIfPresent(Bool.self, forKey: .readOnly) {
self.readOnly = readOnly
}
baseValue = formFieldValue()
}
public func encode(to encoder: Encoder) throws {
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
@ -145,6 +154,8 @@ import UIKit
try container.encode(index, forKey: .index)
try container.encode(selectable, forKey: .selectable)
try container.encode(selectedIndex, forKey: .selectedIndex)
try container.encode(enabled, forKey: .enabled)
try container.encode(readOnly, forKey: .readOnly)
}
}

View File

@ -0,0 +1,13 @@
//
// ClearableModelProtocol.swift
// MVMCoreUI
//
// Created by Matt Bruce on 1/11/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation
public protocol ClearableModelProtocol {
func clear()
}

View File

@ -13,7 +13,6 @@ import UIKit
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
open var model: MoleculeModelProtocol?
private var initialSetupPerformed = false

View File

@ -0,0 +1,64 @@
//
// ClearFormFieldEffectModel.swift
// MVMCoreUI
//
// Created by Matt Bruce on 1/11/22.
// Copyright © 2022 Verizon Wireless. All rights reserved.
//
import Foundation
public class ClearFormFieldEffectModel: FormFieldEffectProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
public static var identifier: String = "clearFormFieldEffect"
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.encodeIfPresent(activatedRuleIds, forKey: .activatedRuleIds)
try container.encodeModels(rules, forKey: .rules)
}
public func setEffect(validity: Bool, field: FormFieldProtocol,form: FormValidator, group: FormGroupRule) {
guard let field = field as? ClearableModelProtocol, validity else { return }
field.clear()
if let updateField = field as? UIUpdatableModelProtocol {
updateField.updateUI?()
}
}
}

View File

@ -16,11 +16,18 @@ public protocol FormFieldProtocol: FormItemProtocol {
/// A place to store the initial value of the field for checking if the value has changed.
var baseValue: AnyHashable? { get set }
///Bool to determine a state that is different from disabled. Readonly values will be sent
///to the server where disabled fields are not
var readOnly: Bool { get set }
/// Returns the value of the field. Used for validations and possibly for sending to server.
func formFieldValue() -> AnyHashable?
}
extension FormFieldProtocol {
var baseValue: AnyHashable? { nil }
var readOnly: Bool { false }
}

View File

@ -111,11 +111,19 @@ import MVMCore
/// - 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)
let tuple = group.validate(fields)
group.rules.forEach { rule in
for formKey in rule.fields {
guard let formField = fields[formKey] as? FormRuleWatcherFieldProtocol,
let fieldValidity = tuple.fieldValidity[formKey] else { continue }
formField.setValidity(fieldValidity, rule: rule)
}
}
// Notify the group watchers of validity.
for watcher in groupWatchers.filter({$0.groupName == group.groupName}) {
watcher.setValidity(valid)
watcher.setValidity(tuple.valid)
}
var ruleChange = false
@ -125,14 +133,14 @@ import MVMCore
//get the fieldKey for the effect
if let effected = fields[effect.fieldKey] {
//get the validity
let validity = effect.validate(fields)
let effectTuple = effect.validate(fields)
//set the effect with the validation
effect.setEffect(validity: validity, field: effected, form: self, group: group)
effect.setEffect(validity: effectTuple.valid, 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)
let didChange = self.updateRules(for: group, with: effectTuple.valid, for: effect.fieldKey, and: ruleIds)
if(didChange) {
ruleChange = didChange
}
@ -147,7 +155,7 @@ import MVMCore
return try self.validateGroup(group, counter: counter + 1)
}
} else {
return valid
return tuple.valid
}
}

View File

@ -51,15 +51,10 @@ public class RuleEqualsIgnoreCaseModel: RulesProtocol {
fieldValidity = false
}
for formKey in fields {
guard let formField = fieldMolecules[formKey] else { continue }
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(true, rule: self)
}
break
}
previousValidity[formKey] = valid
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
}
return (valid: valid, fieldValidity: previousValidity)
}

View File

@ -43,8 +43,6 @@ public class RuleEqualsModel: RulesProtocol {
if compareValue != formField.formFieldValue() {
valid = false
previousValidity[formKey] = valid
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(valid, rule: self)
break
} else {
var fieldValidity = valid
@ -52,8 +50,9 @@ public class RuleEqualsModel: RulesProtocol {
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
}
previousValidity[formKey] = valid
}
return (valid: valid, fieldValidity: previousValidity)
}

View File

@ -53,7 +53,6 @@ public extension RulesProtocol {
if let validity = previousFieldValidity[formKey], !validity, fieldValidity {
fieldValidity = false
}
(formField as? FormRuleWatcherFieldProtocol)?.setValidity(fieldValidity, rule: self)
valid = valid && fieldValidity
previousValidity[formKey] = fieldValidity
}
@ -66,12 +65,19 @@ public protocol RulesContainerProtocol{
}
public extension RulesContainerProtocol {
func validate(_ fields: [String: FormFieldProtocol]) -> Bool {
/// This validation for Rules for the Validation or for Effects.
/// - Parameters:
/// - fields: Fields for the group
/// - setValidity: Since this function is for validation, this bool determines if you should set the FormFields.setValidity for a rule.
/// this method can be called for Form Validation and Effect Validation (this doesn't affect the submital of the form)
/// - Returns: Tuple(valid, fieldValidity)
/// - valid: bool for all rules
/// - fieldValidity: accumulation of all fieldKey: valid
func validate(_ fields: [String: FormFieldProtocol]) -> (valid: Bool, fieldValidity: [String: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)
@ -79,6 +85,6 @@ public extension RulesContainerProtocol {
previousValidity = previousValidity.merging(tuple.fieldValidity) { (_, new) in new }
valid = valid && tuple.valid
}
return valid
return (valid: valid, fieldValidity: previousValidity)
}
}

View File

@ -247,5 +247,6 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(DynamicRuleFormFieldEffectModel.self)
ModelRegistry.register(DisableFormFieldEffectModel.self)
ModelRegistry.register(HideFormFieldEffectModel.self)
ModelRegistry.register(ClearFormFieldEffectModel.self)
}
}