// // Copyright (c) 2018 Touch Instinct // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the Software), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // import LeadKit import RxSwift import RxCocoa import SwiftValidator /// Base implementation of TextFieldViewEvents. open class BaseTextFieldViewEvents: TextFieldViewEvents { public let textChangedDriver: Driver /// Memberwise initializer. /// /// - Parameter textChangedDriver: Driver that emits text changes from a view. public init(textChangedDriver: Driver) { self.textChangedDriver = textChangedDriver } } /// Base implementation of text field view model events. open class BaseTextFieldViewModelEvents: TextFieldViewModelEvents { public let setTextDriver: Driver public let changeValidationStateDriver: Driver public let changeOnlineValidationStateDriver: Driver /// Memberwise initializer. /// /// - Parameters: /// - setTextDriver: Driver that emit text that should be set inside a view. /// - changeValidationStateDriver: Driver that emit validation state changes events. /// - changeOnlineValidationStateDriver: Driver that emit online validation state changes events. public init(setTextDriver: Driver, changeValidationStateDriver: Driver, changeOnlineValidationStateDriver: Driver) { self.setTextDriver = setTextDriver self.changeValidationStateDriver = changeValidationStateDriver self.changeOnlineValidationStateDriver = changeOnlineValidationStateDriver } } public extension BaseTextFieldViewModelEvents { /// Method that binds text driver to validation service via validation rules. /// /// - Parameters: /// - textDriver: Driver that emits text changes. /// - rules: Rules to validate for. /// - validationService: Validation service to register in. /// - Returns: Driver that emit validation state changes. static func offlineValidationDriver(with textDriver: Driver, using rules: [Rule] = [RequiredRule()], in validationService: ValidationService) -> Driver { let validationItem = ValidationItem(rules: rules, textDriver: textDriver) validationService.register(item: validationItem) let validationStateDriver = validationItem .validationStateObservable .asDriver(onErrorJustReturn: .initial) return validationStateDriver } typealias OnlineValidationClosure = (String) -> Single /// Method that binds text driver to validation chain (offline validation -> online validation) /// and returns online validation state driver. /// /// - Parameters: /// - textDriver: Driver that emits text changes. /// - offlineRules: Rules to validate before online validation will be requested. /// - validationClosure: Closure that will be called for online validation request. /// - Returns: Driver that emit online validation state changes. static func onlineValidationDriver(with textDriver: Driver, using offlineRules: [Rule] = [], validationClosure: @escaping OnlineValidationClosure) -> Driver { textDriver.flatMap { string -> Driver in guard let nonEmptyString = string, !nonEmptyString.isEmpty else { return .just(.initial) } let passedRules = offlineRules .allSatisfy { $0.validate(nonEmptyString) } guard passedRules else { return .just(.initial) } let validationDriver = validationClosure(nonEmptyString).map { result -> OnlineValidationState in if result.isValid { return .valid } else { return .invalid(error: result.error) } } .asDriver(onErrorJustReturn: .initial) return Driver.merge([.just(.initial), .just(.processing), validationDriver]) } } /// Convenience initializer with offline and online validation. /// /// - Parameters: /// - binding: Data model field binding. /// - rules: Rules to validate before online validation will be requested. /// - validationService: Validation service to register in. /// - onlineValidationClosure: Closure that will be called for online validation request. /// - onlineValidationThrottle: Throttling duration for each text change. convenience init(binding: DataModelFieldBinding, rules: [Rule] = [RequiredRule()], validationService: ValidationService, onlineValidationClosure: OnlineValidationClosure? = nil, onlineValidationThrottle: RxTimeInterval = .milliseconds(500)) { let dataModelFieldDriver = binding.fieldDriver let offlineValidationDriver = BaseTextFieldViewModelEvents.offlineValidationDriver(with: dataModelFieldDriver, using: rules, in: validationService) let onlineValidationDriver: Driver if let onlineValidationClosure = onlineValidationClosure { let throttledTextDriver = dataModelFieldDriver.throttle(onlineValidationThrottle) onlineValidationDriver = BaseTextFieldViewModelEvents .onlineValidationDriver(with: throttledTextDriver, using: rules, validationClosure: onlineValidationClosure) } else { onlineValidationDriver = .just(.initial) } self.init(setTextDriver: dataModelFieldDriver, changeValidationStateDriver: offlineValidationDriver, changeOnlineValidationStateDriver: onlineValidationDriver) } }