Skip to content

Version 1.0-RC2

Compare
Choose a tag to compare
@jwstegemann jwstegemann released this 25 Nov 14:19
· 251 commits to master since this release
10ec9c3

Breaking Changes

PR #718: Remove Repositories from Core

As we have considered repositories to add no real value as abstraction, this commit will remove them entirely from fritz2.

Migration Guide

Just integrate the code form any repository implementation directly into the handler's code, that used to call the repository. Of course all Kotlin features to structure common code could be applied, like using private methods or alike.

PR #707: Repair remote auth middleware - prevent endless loop for 403 response

The default status code for a failed authentication is reduced to only 401.

Rational

Before also the 403 was part of the status codes and would trigger the handleResponse interception method and starts a new authentication recursively. This is of course a bad idea, as the authorization will not change by the authentication process. Therefore the default http status for launching an authentication process should be only 401.

Migration Guide

If you have some service that really relies on the 403 for the authentication, please adopt to the http semantics and change that to 401 instead.

PR #712: Simplify history feature

Simplifying the history feature, which includes the following changes:

  • history is synced with Store by default
// before
val store = object : RootStore<String>("") {
    val hist = history<String>().sync(this)
}
// now
val store = object : RootStore<String>("") {
    val hist = history() // synced = true
}
  • renamed reset() method to clear()
  • renamed add(entry) method to push(entry)
  • removed last() method, cause with current: List<T> every entry is receivable
  • changed default capacity to 0 (no restriction) instead of 10 entries

PR #715: Exposing Store interface instead of internal RootStore and SubStore

Exposing only the public Store<T> type in fritz2's API, instead of the internal types RootStore or SubStore for simplifying the use of derived stores.

// before
val person: RootStore<Person> = storeOf(Person(...))
val name: SubStore<Person, String> = person.sub(Person.name())

// now
val person: Store<Person> = storeOf(Person(...))
val name: Store<String> = person.sub(Person.name())

Migration Guide

Just change the type of some field or return type from RootStore<T> to Store<T> and SubStore<T, D> to Store<D>.

PR #727: Resolve bug with alsoExpression on Hook with Flow

In order to make the also-expression work with Flow based payloads, we had to tweak the API.
The Effect now gets the alsoExpr from the Hook injected into the applied function as second parameter besides the payload itself. This way the expression can and must be called from the value assigning code sections, which a hook implementation typically implements.

As the drawback we can no longer expose the return type R to the outside client world. An effect now returns Unit.

typealias Effect<C, R, P> = C.(P, (R.() -> Unit)?) -> Unit
                                  ^^^^^^^^^^^^^^^
                                  alsoExpr as 2nd parameter

migration guide

The client code of some hook initialization does not need any changes.

The code for hook execution should almost always stay the same, as long as the code did not rely on the return type. If that was the case, you have the following options:

  1. move operating code into the hook implementation itself
  2. if some external data is needed, enrich the payload with the needed information and the proceed with 1.

The assignment code to Hook.value will need a second parameter. Often this is done by some functional expression, which can be solved like this (example taken from TagHook):

// before
operator fun invoke(value: I) = this.apply {
    this.value = { (classes, id, payload) ->
        renderTag(classes, id, value, payload)
    }
}

// now
operator fun invoke(value: I) = this.apply {
    this.value = { (classes, id, payload), alsoExpr ->
                                        // ^^^^^^^^
                                        // add 2nd parameter
        renderTag(classes, id, value, payload).apply { alsoExpr?.let { it() } }
                                            // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                            // apply the expression onto the specific result (`R`)
                                            // is is always specific to the hook's implemetation
    }
}

New Features

PR #701: Add headless Toast component

A toast is a component that can be displayed in specific areas of the screen for both a fixed or indefinite amount of time, similar to notifications. fritz2's headless components now offer a nice and simple abstraction for this kind of functionality.

Have a look into our documentation to get more information.

PR #719: Enable Event capturing

It is now possible to listen on events in capture-phase (suffixed by Captured):

render {
    div {
        clicksCaptured handledBy store.save
    }
}

For this we added new options to the subscribe() function which gives you a Listener for your event:

subscribe<Event>(name: String, capture: Boolean, init: Event.() -> Unit)

Using the init-lambda you can make settings to the captured event that have to be applied immediately.

We also fixed a bug when using stopPropagation() on a Listener which sometime did not work as expected.

Further New Features

  • PR #716: Integrate fritz2 examples into the web site

Improvements

PR #711: Improve handling of nullable values in Stores

Handling nullable values in Stores

If you have a Store with a nullable content, you can use orDefault to derive a non-nullable Store from it, that transparently translates a null-value from its parent Store to the given default-value and vice versa.

In the following case, when you enter some text in the input and remove it again, you will have a value of null in your nameStore:

val nameStore = storeOf<String?>(null)

render {
    input {
        nameStore.orDefault("").also { formStore ->
            value(formStore.data)
            changes.values() handledBy formStore.update
        }
    }
}

In real world, you will often come across nullable attributes of complex entities. Then you can often call orDefault directly on the SubStore you create to use with your form elements:

@Lenses
data class Person(val name: String?)

//...

val applicationStore = storeOf(Person(null))

//...

val nameStore = applicationStore.sub(Person.name()).orDefault("")

Calling sub on a Store with nullable content

To call sub on a nullable Store only makes sense, when you have checked, that its value is not null:

@Lenses
data class Person(val name: String)

//...

val applicationStore = storeOf<Person>(null)

//...

applicationStore.data.render { person ->
    if (person != null) { // if person is null you would get NullPointerExceptions reading or updating its SubStores
        val nameStore = customerStore.sub(Person.name())
        input {
            value(nameStore.data)
            changes.values() handledBy nameStore.update
        }
    }
    else {
        p { + "no customer selected" }
    }
}

Further Improvements

  • PR #696: Upgrades to Kotlin 1.7.20
  • PR #677: Improve textfield API
  • PR #681: Improve Headless Input API
  • PR #680: Make render's lambda run on Tag instead of RenderContext
  • PR #686: Add default data-binding as fallback for headless components
  • PR #692: Improve DataCollection behavior: Let selections be updated by filtered data flow
  • PR #699: Added link for docs to edit the content on Github
  • PR #705: Improve http example in documentation
  • PR #706: Rework documentation for Webcomponents
  • PR #708: Detect missing match in RootStore for IdProvider based derived stores
  • PR #726: Improve the Focustrap for Flow based sections

Fixed Bugs

  • PR #663: Fix structure info in validation handling
  • PR #679: Fix for Attribute referenced id
  • PR #687: Repair aria-haspopup for PopUpPanel based components
  • PR #688: Add default z-Index for PopUpPanel
  • PR #689: Fix ModalPanel id being overridden
  • PR #690: Improve Focus Management on various headless Components
  • PR #694: Improves OpenClose's toggle behaviour