Eureka: Reusing validation display code

Created on 12 Jul 2017  Â·  3Comments  Â·  Source: xmartlabs/Eureka

I have a required row:

<<< TextRow { row in
    row.title = "Full Name"
    row.add(rule: RuleRequired())
}

Now I want to display the validation errors. A suggested solution is to use the cellUpdate callback:

<<< TextRow { row in
    row.title = "Full Name"
    row.add(rule: RuleRequired())
}.cellUpdate { cell, row in
    if !row.isValid {
        cell.titleLabel?.textColor = .red
    }
}

This works, but has to be done for each row. So if I have 10 required rows, the code has to be repeated 10 times. That’s clearly not perfect. I have tried to introduce a helper method:

<<< TextRow { row in
    row.title = "Full Name"
    row.add(rule: RuleRequired())
}.cellIUpdate(highlightValidationErrors)

func highlightValidationErrors(cell: Cell, row: Row) { … }

But here I am fighting the type system, since I can’t find a decent highlightValidationErrors type that would fit different row types. I would like even better if I could hook up all required fields automatically after creating the form:

    for row in form.allRows {
        // hook validation error code
    }

But how do get to the cellUpdate hook that way? Surely there must be a good solution I am missing, since I can’t imagine people just repeating the validation display code over and over.

Validations

Most helpful comment

Thank you! It would be great if I could attach default handlers to a particular form. The more specific handler would then trump the class-wide defaults. But for now I am happy with the class-wide defaults. This is the code I came up with to share the validation logic between different row types:

extension Form {

    static func installDefaultValidationHandlers() {
        TextRow.defaultCellUpdate = highlightCellLabelIfValidationFailed
        TextRow.defaultOnRowValidationChanged = showValidationErrors
    }

    private static func highlightCellLabelIfValidationFailed(cell: BaseCell, row: BaseRow) {
        if !row.isValid {
            cell.textLabel?.textColor = .red
        }
    }

    private static func showValidationErrors(cell: BaseCell, row: BaseRow) {
        row.removeValidationErrorRows()
        row.addValidationErrorRows()
    }
}

extension BaseRow {

    fileprivate func removeValidationErrorRows() {
        let rowIndex = indexPath!.row
        while section!.count > rowIndex + 1 && section?[rowIndex  + 1] is LabelRow {
            _ = section?.remove(at: rowIndex + 1)
        }
    }

    fileprivate func addValidationErrorRows() {
        for (index, validationMsg) in validationErrors.map({ $0.msg }).enumerated() {
            let labelRow = LabelRow {
                $0.title = validationMsg
                $0.cell.height = { 30 }
            }
            section?.insert(labelRow, at: indexPath!.row + index + 1)
        }
    }
}

It’s fairly obvious in hindsight, but took me a while to find the correct parent type to attach the shared logic to. Hopefully this helps someone.

All 3 comments

I see, I have just now discovered TextRow.defaultCellUpdate and friends. Would it be possible to apply those defaults to a particular form instance only?

Hi!
By checking the form instance from TextRow.defaultCellUpdate closure, something like row.section?.form === theRelevantForm

There is no other way...

Thank you! It would be great if I could attach default handlers to a particular form. The more specific handler would then trump the class-wide defaults. But for now I am happy with the class-wide defaults. This is the code I came up with to share the validation logic between different row types:

extension Form {

    static func installDefaultValidationHandlers() {
        TextRow.defaultCellUpdate = highlightCellLabelIfValidationFailed
        TextRow.defaultOnRowValidationChanged = showValidationErrors
    }

    private static func highlightCellLabelIfValidationFailed(cell: BaseCell, row: BaseRow) {
        if !row.isValid {
            cell.textLabel?.textColor = .red
        }
    }

    private static func showValidationErrors(cell: BaseCell, row: BaseRow) {
        row.removeValidationErrorRows()
        row.addValidationErrorRows()
    }
}

extension BaseRow {

    fileprivate func removeValidationErrorRows() {
        let rowIndex = indexPath!.row
        while section!.count > rowIndex + 1 && section?[rowIndex  + 1] is LabelRow {
            _ = section?.remove(at: rowIndex + 1)
        }
    }

    fileprivate func addValidationErrorRows() {
        for (index, validationMsg) in validationErrors.map({ $0.msg }).enumerated() {
            let labelRow = LabelRow {
                $0.title = validationMsg
                $0.cell.height = { 30 }
            }
            section?.insert(labelRow, at: indexPath!.row + index + 1)
        }
    }
}

It’s fairly obvious in hindsight, but took me a while to find the correct parent type to attach the shared logic to. Hopefully this helps someone.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Tomas1405 picture Tomas1405  Â·  3Comments

abbasmousavi picture abbasmousavi  Â·  3Comments

fpena picture fpena  Â·  3Comments

allanrojas picture allanrojas  Â·  3Comments

thlbaut picture thlbaut  Â·  3Comments