Version 1.0-RC2
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 toclear()
- renamed
add(entry)
method topush(entry)
- removed
last()
method, cause withcurrent: List<T>
every entry is receivable - changed default
capacity
to0
(no restriction) instead of10
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:
- move operating code into the hook implementation itself
- 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 Store
s
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