Easily build reactive web-apps in Kotlin based on flows and coroutines.

Overview

fritz2

Actions Status Awesome Kotlin Badge Download IR Kotlin Examples API Docs

fritz2 is an extremely lightweight, well-performing, independent library to build reactive web apps in Kotlin heavily depending on coroutines and flows.

fritz2 includes an intuitive way to build and render html-elements and styles using a type-safe dsl. You can easily create lightweight reactive html-components that are bound to an underlying model and automatically change whenever the model data changes:

val model = storeOf("init value")

render {
    div("some-css-class") {
        input {
            value(model.data)
            changes.values() handledBy model.update 
        }
        p {
            +"model value = "
            model.data.asText()
        }
    }
}

fritz2 implements precise data binding. This means that when parts of your data model change, exactly those and only those DOM-nodes depending on the changed parts will automatically change as well. No intermediate layer (like a virtual DOM) is needed. fritz2 requires no additional methods to decide which parts of your component have to be re-rendered:

State management in fritz2

Utilizing Kotlin's multiplatform-abilities, you'll write the code of your data classes only once and use it on your client and server (i.e. in a SpringBoot- or Ktor-Backend). This is also true for your model-validation-code, which can quickly become far more complex than your data model.

Key Features

  • easy reactive one- and two-way-databinding (even for lists and deep nested structures)
  • hassle-free redux-like state-handling
  • model-validation and message handling
  • integrated styling-dsl
  • component-library
  • routing (for SPAs, hash-based)
  • backend-repositories (Rest APIs, WebSockets, LocalStorage, etc.)
  • history / undo
  • processing state ("spinning wheel")
  • easy to learn
  • documentation
  • examples i.e. implementing the specification of TodoMVC

How to try it?

Overall Goals

  • staying lightweight (a few hundred lines of code for the core)
  • keeping dependencies as low as possible (zero up to now!)
  • generating elements, attributes, events for html from specification (w3c, mozilla, ...)
  • make it as easy as possible to write reactive pwas in pure kotlin

Inspiration

fritz2 is hugely inspired by the great Binding.scala. Later we discovered that a lot of those concepts are described independently in Meiosis.

Leave us a star...

If you like the idea of a lightweight pure Kotlin implementation for building reactive web apps, please give us a star on github. Thank you.

Comments
  • Full Stack Example

    Full Stack Example

    We need an example sharing model- and validation-code between front and backend and shows how to packe and deploy the combination in

    • Spring boot
    • ktor
    • kotless
    enhancement discussion good first issue 
    opened by jwstegemann 18
  • Menu clicks in dropdown does not works

    Menu clicks in dropdown does not works

    Describe the bug I have a menu inside dropdown. Click events on menu items does not work.

    To Reproduce

    1. https://components.fritz2.dev/#Menu
    2. Scroll down to Dropdown Menus.
    3. Click on the dropdown hamburger icon, the menu appears.
    4. Click on Clickable entry.
    5. Based on the menu above, toast should appear, but nothing happens!

    Expected behavior Toast appears

    Desktop (please complete the following information):

    • OS: Linux
    • Browser Chrome
    • Version 93
    opened by Pitel 16
  • Running a slow suspend function on `Flow` that's to be rendered makes UI freeze

    Running a slow suspend function on `Flow` that's to be rendered makes UI freeze

    Hi!

    I'm running a slow suspend function (~4.5s) that's running a WASM method. The whole UI completely freezes. The code is something like

    val store: RootStore
    val otherStore: RootStore
    
    render {
      img { src(otherStore.data) }
    
      store.data.map { data -> callSuspendFunction(data) } handledBy otherStore.update
    }
    

    How am I supposed to use coroutines correctly? I've tried using withContext and other solutions that don't help.

    opened by Lundez 14
  • Wrong compile error in IDE for generated Lenses

    Wrong compile error in IDE for generated Lenses

    Sometimes there is an error in your IDE with the generated L object after the Gradle build were successfull.

    val name = inspector.sub(L.Person.name)
    

    This is of course confusing and we should try to check if we can tell the IDE where to find this generated sources.

    opened by jamowei 12
  • maintenance release for 0.14 branch with updated dependencies

    maintenance release for 0.14 branch with updated dependencies

    It looks like switching to 1.0 is going to be a major project for us due to the deprecation of the current component library. Realistically, we won't be able to do this shortly because we have other priorities right now. Would it be possible to do a maintenance release for the 0.14 branch with updated dependencies? We are kind of blocked on updating a lot of our internal dependency because of this.

    I'd be happy to do a pull request for this of course.

    dependencies 
    opened by jillesvangurp 10
  • Menu Entries in Dropdowns do not always react to click events

    Menu Entries in Dropdowns do not always react to click events

    When using a Menu inside a Dropdown, the click event of the entry is now always triggered. Fast clicks usually work, but with slower clicks the dropdown gets hidden on mouse down, which prevents the click event for the menu entry.

    The issue is also reproducible in the KitchenSink (https://components.fritz2.dev/#Menu) under Dropdown Menus. The Clickable Entry in the dropdown does not always show a toast message.

    bug 
    opened by metin-kale-cf 8
  • Change type of ``RenderContext`` to make it an universal receiver for rendering

    Change type of ``RenderContext`` to make it an universal receiver for rendering

    • solves #228
    • make RenderContext a type alias for Tag<HtmlElements> in order to make it a fitting and universal receiver type for rendering
    • all components should not need changes, as they accidentally yet refer to this currently false type
    • need to test all example projects too!
    opened by chausknecht 7
  • datatable broken in safari & ios

    datatable broken in safari & ios

    Describe the bug

    I have a simple application with a data table. It misrenders in safari. The table rows are appearing next to the header instead of below.

    To Reproduce

    data class Foo(val foo:String)
    
    render("#target") {
            val s = storeOf(listOf(Foo("foo"), Foo("bar"), Foo("foobar")))
            val fooLens = buildLens<Foo, String>(
                id = "fooCol",
                getter = { it.foo },
                setter = { old, v -> old.copy(foo = v) })
            val selected = storeOf<Foo?>(null)
            s.data.render {
                dataTable(rows = s, rowIdProvider = { it.foo }, selection = selected) {
                    columns() {
                        column(title = "Foo") {
                            lens(fooLens)
                        }
                    }
                }
            }
    }
    
    

    Expected behavior

    rows appear below each other

    Screenshots

    image

    Desktop (please complete the following information):

    • MacOS / IOS
    • Safari
    • Fritz 0.13
    opened by jillesvangurp 6
  • TypeAhead causes ClassCastException with Popover/Keyup-Events

    TypeAhead causes ClassCastException with Popover/Keyup-Events

    When using a typeahead on the same page as a popover, selecting an item from a typeAhead causes a ClassCastException, which stops JS Execution on the page.

    Here is a minimal example:

    fun main() {
        render(DefaultTheme()) {
            div {
                popover {
                    toggle { div { +"Popover" } }
                    content { div { +"Hello" } }
                }
                typeAhead(value = storeOf(""), items = { flow ->
                        flow.map { (1..50).map { it.toString() } }
                })
            }
        }
    }
    

    After some investigation i found, that the issue is the keyup listener in the Popover. #517 might be a partial solution, but other keyup listeners will still cause an issue.

    The problem is, that fritz2 assumes to get a 'KeyboardEvent' from the keyup listener, but in case of selecting an item from a datalist, the event looks different, it neither has 'key' or 'keyCode' properties.

    bug 
    opened by metin-kale-cf 6
  • FromControl label should have

    FromControl label should have "for" attribute

    Describe the bug FromControl label should have "for" attribute, so when user click on the label, the input gets automaticaly focused.

    To Reproduce Steps to reproduce the behavior:

    1. Create simple formControl with label and inputField.
    2. Click on the label.
    3. Nothing happens.

    Expected behavior Cursor will appear inside the inputField (or, in case of chackbox, the checbox is toggled)

    Additional context Or, you can nest the <input>inside

    https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

    bug api breaking 
    opened by Pitel 6
  • Unclear error handling

    Unclear error handling

    Is your feature request related to a problem? Please describe. I'm learning Fritz2, and I'm making a login form. When user enters bad credentials, and the API returns HTTP 401, I'd like to show the error (toast). I can't find a way how.

    Another related issue is in Tracker. When I start the remote request, and it fails, the button keeps spinning and I couldn't find any method how to stop it.

    Describe the solution you'd like

    • Make special chapter in documentation about error handling.
    • Demonstrate it in remotes example.
      • I have uBlock, which blocks the request to the domain you use in this example. I see the error in developer console, but nothing in the UI.

    Abouty the tyracker problem, I'd suggest to change the track method like this:

    // OLD (0.10.1)
    return operation().also { state.value = null } 
    
    // NEW
    return try { operation() } finally { state.value = null }
    

    Describe alternatives you've considered I couldn't find any. Just provide some documentation and example.

    Additional context My handler looks like this. If you could show me how to display the error toast, I'd be grateful.

    val login = handle { model ->
        loading.track {
            if (validator.isValid(model, Unit)) {
                try {
                    http("/apiIs2/authUser")
                        .acceptJson()
                        .contentType("application/json")
                        .body(
                            JSON.stringify(
                                json(
                                    "username" to model.login, "password" to model.password
                                )
                            )
                        )
                        .post()
                        .getBody()
                    router.navTo("main")
                } catch (t: Throwable) {
                    console.log(t)
                    toast { // NOT DISPLAYED :(
                        content {
                            alert {
                                title("Login error")
                                content(
                                    if (t is FetchException && t.statusCode == 401.toShort()) {
                                        "Bad credentials"
                                    } else {
                                        "$t"
                                    }
                                )
                            }
                        }
                    }
                }
            }
            model
        }
    }
    
    bug 
    opened by Pitel 6
  • Add missing data-attribute for mountpoints in renderText

    Add missing data-attribute for mountpoints in renderText

    As for the other render* methods the renderText methods should also mark the DOM element that acts as the mountpoint with the custom data-attribute data-mount-point.

    This is currently missing.

    good first issue 
    opened by chausknecht 0
  • Timing problem with leave-animations

    Timing problem with leave-animations

    Leave animations can cause problems when the next patch(es) for a mount-point are applied before the animation is completed. This is because the patches work on indices of child-elements. When the dom-elements of animated tags are still there when the next patch is applied the indices calculated by the diff are not correct. The effect is, that some dom-nodes corresponding to removed elements are never deleted.

    Possible solution:

    Tag elements with a running leave-animation (for example by data-leaving) and do not count them in anymore, when patches are applied.

    bug 
    opened by jwstegemann 0
  • Rework Closing of Headless Toast by ESC key to Event Capturing

    Rework Closing of Headless Toast by ESC key to Event Capturing

    Currently the headless toast component (see PR #701) enables one to close the toasts by ESC-key. This conflicts with other components (PopUpPanel based ones) which also register their closing on the window level.

    If event capturing is available in fritz2, we should use the former for the ESC-key event handling. This way we can then stop the propagation, if there was at least one toast to close. This way all other components would not react.

    depends on #552

    headless 
    opened by chausknecht 0
  • Add some ComboBox aka TypeAhead to the headless components

    Add some ComboBox aka TypeAhead to the headless components

    We lack some very important headless component, that we should add to our foundation: A ComboBox or TypeAhead (see also headless-ui

    This element is used to select one or many items from some "large" list of elements; could be static or generated from dynamic remote queries on the fly during typing.

    We should investigate, how to manage (display and unselect) multiple selections. If there is some senseful headless approach, we should use that for the ListBox and our DataCollection as well. (might be a different issue of course)

    headless 
    opened by chausknecht 0
  • Provide support for

    Provide support for "masks" in InputField component

    As a common use case, an InputField should only allow some well defined patterns, like only numbers in a specific format like money amounts or credit card numbers and so on. This is where so called "masks" come into play.

    As a really neat feature we should try to find some good support for such uses cases on the "headless" level API of InputField component.

    We should have a look at https://imask.js.org/

    Key questions to solve:

    • abstraction over the possibilities of the underlying JS lib or better expose its API?
    • make its usage optional!
    discussion headless 
    opened by chausknecht 0
Releases(v1.0-RC2)
  • v1.0-RC2(Nov 25, 2022)

    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
    Source code(tar.gz)
    Source code(zip)
  • v0.14.4(Aug 3, 2022)

  • v1.0-RC1(Jun 17, 2022)

    Breaking Changes

    PR #567: Drop old Components

    We are sorry to announce, that we have dropped our so far developped components. As this is a huge move, we have written an article where we explain our motivation and introduce the new approach we take from now on.

    They will remain of course part of the long term supporting 0.14 release line, which we plan to support until the end of this year approximately. This should offer you enough time to migrate to the new headless based approach.

    Nevertheless, if you really want to keep those components alive and dare the task to maintain them on your own, feel free to extract them out of fritz2 and provide them as your own project. Feel free to contact us if you need some help.

    PR #582: Change Structure of basic Types

    • Tag<> is now an Interface
    • There are two implementations available for HtmlTag<> and SvgTag<>
    • The specialized classes for individual Tags like Div, Input, etc. have been removed
    • Attributes specific for individual tags are available as extension functions (and have to be imported).

    Migration-Path

    Wherever you used specialized classes that inherited from Tag<> like Div, Input, etc., just exchange this by Tag<HTMLDivElement> or Tag<HTMLInputElement>.

    If you access specific attributes of a certain Tag<> like value on an input, just import it from dev.fritz2.core.*.

    PR #596: New package structure

    We simplyfied our package structure, we used in fritz2, to minimze the amount of import statements. This means that you can now often use the wildcard import (import dev.fritz2.core.*), which makes calling the new attribute extension functions on the Tag<> interface (#582) much easier.

    before:

    import dev.fritz2.binding.RootStore
    import dev.fritz2.binding.SimpleHandler
    import dev.fritz2.binding.Store
    import dev.fritz2.dom.html.Div
    import dev.fritz2.dom.html.RenderContext
    import dev.fritz2.dom.html.render
    import dev.fritz2.dom.states
    import dev.fritz2.dom.values
    

    now:

    import dev.fritz2.core.*
    

    PR #584: API-streamlining of fritz2 core

    Following changes takes place:

    • global keyOf function for creating a Scope.Key is moved to Scope class
    // before
    val myKey = keyOf<String>("key")
    // now
    val myKey = Scope.keyOf<String>("key")
    
    • all repository factory-functions ends with Of appendix
    // before
    val localStorage = localStorageEntity(PersonResource, "")
    // now
    val localStorage = localStorageEntityOf(PersonResource, "")
    
    • renaming buildLens function to lens and elementLens and positionLens to lensOf for lists
    // before 
    val ageLens = buildLens(Tree::age.name, Tree::age) { p, v -> p.copy(age = v) }
    val elementLens = elementLens(element, id)
    val positionLens = positionLens(index)
    
    // now
    val ageLens = lens(Tree::age.name, Tree::age) { p, v -> p.copy(age = v) }
    val elementLens = lensOf(element, id)
    val positionLens = lensOf(index)
    
    • replacing UIEvent by Event which solves ClassCastExceptions when UIEvent is explicitly needed you have to cast it (see #578)
    • removed special attr function for Map and List. Convert them by yourself to a String or Flow<String> and use then the attr function.
    // before
    attr("data-my-attr", listOf("a", "b", "c")) // -> data-my-attr="a b c"
    attr("data-my-attr", mapOf("a" to true, "b" to false)) // -> data-my-attr="a"
    
    // now
    attr("data-my-attr", listOf("a", "b", "c").joinToString(" "))
    attr("data-my-attr", mapOf("a" to true, "b" to false).filter { it.value }.keys.joinToString(" "))
    

    PR #585: Rework fritz2 core event concept

    By using delegation a Listener is now a Flow of an event, so you can directly use it without the need to use the events attribute. Also the distinction between DomListener and WindowListener is not needed anymore.

    // before
    keydowns.events.filter { shortcutOf(it) == Keys.Space }.map {
        it.stopImmediatePropagation()
        it.preventDefault()
        if (value.contains(option)) value - option else value + option
    }
    
    // now
    keydowns.stopImmediatePropagation().preventDefault()
        .filter { shortcutOf(it) == Keys.Space }
        .map { if (value.contains(option)) value - option else value + option }
    

    PR #591: Job handling improvements

    • we removed the syncBy() function, as it was not useful enough and easily to misunderstand.
    • to prevent possible memory leaks, we moved syncWith to WithJob interface

    PR #622: Fix invocation of Handlers

    • invoke-extensions to directly call handlers have been moved to WIthJob and can easily be called from the context of a Store or a RenderContext only.

    New Features

    New Webpage

    We are happy to announce that we have reworked our whole web presence. We have moved to 11ty as base, so we are able to integrate all separate pieces into one consistent page:

    • landing page
    • documentation
    • new headless components
    • blog / articles

    We are planning to integrate also the remaining examples and to add further sections like recipes.

    Besides the pure visual aspects (and hopefully improvements) this improves our internal workflows a lot; it is much easier to coordinate the development of changes and new features along with documentation, examples and possibly some recipe we have identified. Also issues and pull requests will reside inside the fritz2 project itself and thus improve the overall workflow.

    We hope you enjoy it :-)

    Headless Components

    We are proud to announce a new way to construct UIs and possibly reusable components: Headless Components

    We are convinced those will improve the creation of UIs with consistent functionality combinded with context fitting structure and appearance.

    If you are interested how we have arrived to this paradigm shift, we encourage you to read this blog post.

    PR #641: Add structural information for headless components

    In order to improve the usage of headless components all components and its bricks will render out HTML comments that name their corresponding component or brick name. This way the matching between the Kotlin names and its HTML equivalents is much easier.

    Most hint comments are located as direct predecessor of the created HTML element. For all bricks that are based upon some Flow this is not possible due to their managed nature. In those cases the comment appears as first child-node within the created element and its text startes with "parent is xyz" to clarify its relationship.

    In order to activate those helpful structural information, one must put the SHOW_COMPONENT_STRUCTURE key into the scope with a true value.

    Example:

    div(scope = { set(SHOW_COMPONENT_STRUCTURE, true) }) {
         switch("...") {
             value(switchState)
         }
    }
    // out of scope -> structural information will not get rendered
    switch("...") {
        value(switchState)
    }
    

    Will result in the following DOM:

    <div>
        <!-- switch -->
        <button aria-checked="false" ...></button>
    </div>
    <button aria-checked="false" ...></button>
    

    PR #570: New Validation

    In order to reduce the boilerplate code, reduce the dependencies to fritz2's core types and to offer more freedom to organize the validation code, we have created a new set of validation tools within this release.

    First, you need to specify a Validation for your data-model. Therefore, you can use one of the two new global convenience functions:

    // creates a Validation for data-model D with metadata T and validation-messages of M
    fun <D, T, M> validation(validate: MutableList<M>.(Inspector<D>, T?) -> Unit): Validation<D, T, M>
    
    // creates a Validation for data-model D and validation-messages of M
    fun <D, M> validation(validate: MutableList<M>.(Inspector<D>) -> Unit): Validation<D, Unit, M>
    

    These functions are available in the commonMain source set, so you can create your Validation object right next to your data classes to keep them together. Example:

    @Lenses
    data class Person(
        val name: String = "",
        val height: Double = 0.0,
    ) {
        companion object {
            val validation = validation<Person, String> { inspector ->
                if(inspector.data.name.isBlank()) add("Please give the person a name.")
                if(inspector.data.height < 1) add("Please give the person a correct height.")
            }
        }
    }
    

    Then you can call your Validation everywhere (e.g. JVM- or JS-site) to get a list of messages which shows if your model is valid or not. We recommend extending your validation messages from the ValidationMessage interface. Then your validation message type must implement the path which is important for matching your message to the corresponding attribute of your data-model and the isError value which is needed to know when your model is valid or not:

    data class MyMessage(override val path: String, val text: String) : ValidationMessage {
        override val isError: Boolean = text.startsWith("Error")
    }
    
    // change your Validation to use your own validation message
    val validation = validation<Person, MyMessage> { inspector ->
        val name = inspector.sub(Person.name())
        if (name.data.isBlank())
            add(MyMessage(name.path, "Error: Please give the person a name."))
    
        val height = inspector.sub(Person.height())
        if (height.data < 1)
            add(MyMessage(height.path, "Error: Please give the person a correct height."))
    }
    
    // then you can use the valid attribute to check if your validation result is valid or not
    val messages: List<MyMessage> = Person.validation(Person())
    messages.valid  // == false
    

    New ValidatingStore

    We introduce a new type of Store which we call ValidatingStore. This Store has the same properties as a RootStore and additionally a Validation which it uses to validate the stored model. With these additional information you get next to your known data flow also a messages flow which you can use to render out your list of validation messages. Furthermore, you can decide on store creation if you want to automatically validate your model after an update to your store takes place. Then you have to set the validateAfterUpdate flag and the flow of messages gets accordingly updated. Otherwise, you can call the validate(data: D, metadata: T? = null) function inside your own handlers to update the list of messages by your own. For cases, you want to reset your validation state to an empty list or to a specific list of messages, you can use the resetMessages(messages: List<M> = emptyList()) function. Example:

    // create your ValidatingStore
    val myValidationStore = object : ValidatingStore<Person, Unit, MyMessage>(
        Person(), Person.validation, validateAfterUpdate = true, id = "myPerson"
    ) {
        // check if your model is valid and then save
        val save = handle { person ->
            if(validate(person).valid) {
                localStorage.setItem(person.name, person.height.toString())
                Person()
            } else person
        }
        //...
    }
    
    render {
        //...
        input {
            val name = myValidationStore.sub(Person.name())
            value(name.data)
            changes.values() handledBy name.update
        }
        //...
        button {
            +"Save"
            clicks handledBy myValidationStore.save
        }
    
        // render out the messages
        myValidationStore.messages.renderEach(MyMessage::path) {
            p {
                +it.text
                if(it.isError) inlineStyle("color: red")
            }
        }
    }
    

    For further information, have a look at our documentation

    PR #580: Transitions controlled by Flow

    In addition to transitions that are run, when a Tag is added to or removed from the DOM, you can now also apply a Transition to a mounted Tag whenever a new value appears on a Flow[Boolean]:

    tag.transition(someFlowOfBoolean,
        "transition duration-100 ease-ease-out",
        "opacity-0 scale-95",
        "opacity-100 scale-100",
        "transition duration-100 ease-ease-out",
        "opacity-100 scale-100",
        "opacity-0 scale-95"
    )
    

    The enter-transition will be executed when true appears on the [Flow] The leave-transition will be executed when false appears on the [Flow]

    We also added a suspending extension-function WithDomNode<*>.waitForAnimation, that allows you to wait for running transitions to be finished. This is for example useful, if you want to make a parent element invisible only after a the fading-out of it's children is done.

    PR #587: Add new functions to the core

    DomNodeList

    In order to integrate the NodeList type as the result of lots of DOM-queries better into Kotlin's List-API, the new type DomNodeList was added to fritz2. It acts as an adapter on top of a NodeList and enables the applications of the common List operations.

    In order to constuct such a DomNodeList from some query result, the extension function asElementList is offered:

    val amount = document
        .querySelectorAll("p")
        .asElementList() // create `DomNodeList`
        .filter { it.innerText.contains("fritz2") }
        .count()
    

    Extension function whenever

    As common pattern a tag attribute should appear or disappear based upon some Flow<Boolean> based condition. In order to achieve this effect, the new pair of extension functions T.whenever(condition: Flow<Boolean>): Flow<T?> and Flow<T>.whenever(condition: Flow<Boolean>): Flow<T?> were added:

    val messages: Flow<List<ValidationMessage>> = TODO() // some validation result
    // attribute should only appear if there are errors!
    attr("aria-invalid", "true".whenever(!messages.valid))
    

    Further Improvements

    • PR #592: Added toString() method to Scope.Key class
    • PR #594: Improve error handling when dealing with lenses

    Fixed Bugs

    • PR #568: Authentication.complete() function sets the principal also without running auth-process
    • PR #571: Authentication.getPrincipal() function returns the principal also when pre-setting it before any process is started
    • PR #579: Fixed exception handling in Handlers
    Source code(tar.gz)
    Source code(zip)
  • v0.14.3(Jun 13, 2022)

  • v0.14.2(Jan 31, 2022)

    Fixed Bugs

    • PR #571: Authentication.current property returns the current principal also when pre-setting it before any process is started
    • Reactivate the files component for multi file upload. It has been accidentally deactivated during upgrade to Kotlin 1.6.0.
    Source code(tar.gz)
    Source code(zip)
  • v0.14.1(Jan 19, 2022)

    Improvements

    Improve rendering speed of data table

    This small patch version just improves the rendering speed of the data table component. It tremendously reduces the speed of the cell rendering, by sacrificing the evaluation of changes of a single <td>. Instead, the whole row will be scanned for changes, which is a far better solution in the tradeoff between precise rendering and creating the fitting flow of data from the bunch of pure data, sorting and selection information.

    This does not affect the API of the component, nor the functionality at all, so there is no need to change anything in client code!

    Other Improvements

    • PR #568: Authentication.complete() function sets the principal also without running auth-process
    Source code(tar.gz)
    Source code(zip)
  • v0.14(Jan 17, 2022)

    Important Notes for Apple Users

    • If you use an Apple computer with Apple silicon architecture please make sure to use a JDK targeted to this architecture. It is officially named aarch64 (see JEP 391). Starting with Java 17 all major distributors of JDKs should support this.
    • You will need the following piece of code in your build.gradle.kts:
      rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
          rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().nodeVersion = "16.0.0"
      }
      

      This is already included in the current version of our fritz2-template project. This workaround should be needed only until Kotlin version 1.6.20

    Breaking Changes

    PR #544: Rework RenderContext

    • RenderContext is now an Interface offering all functions to render both dynamic and static content
    • A Tag is an implementation of this interface which adds everything needed to handle a DOM-element
    • TagContext has been removed
    • render*-methods now by default add a new Div-Tag into the DOM which acts as the mountpoint. Those divs are marked with the attribute data-mount-point and the static default class mount-point. The latter simply sets the display: contents rule in order to hide this element from the visual rendering:
      data.render {
          // render something
      }
      

      will result in the following HTML structure:

      <div class="mount-point" data-mount-point=""
          <!-- your dynamic content -->
      </div>
      
    • render*-methods in RenderContext now offer a new parameter into which can be used to bypass the creation of a default parent Div-tag as described above. If there is already an element which could serve as mountpoint, simply pass its Tag<HTMLElement> as the into parameter. Rule of thumb: Just pass this, since the call to render should appear directly below the desired parent element in most cases:
      ul { // this == Tag<Ul>
          items.data.render(into = this) {
          //                ^^^^^^^^^^^
          //                pass the parent tag to use it as mount-point
              li { +it }
          }
      }
      

      This results in the following DOM:

      <ul data-mount-point=""> // no more CSS-class, as the tag *should* appear on screen in most cases
          <li>...</li>
          ...
          <li>...</li>
      </ul>
      

      But beware: the mount-point controls its siblings, meaning that all other content will be removed by every update of the flow. So never apply the into = this pattern if you want to render multiple different flows into a parent, or a mixture of dynamic and static content.

    • instead of asText use renderText. It will create a Span-Tag as parent element and a child text-node for the text. Analogous to the render*-methods of RenderContext, it also accept the into = this parameter, which directly renders the text-node into the parent provided without creating the extra <span>.

    PR #559: Apply KSP as substitute for KAPT for automatic lenses generation

    Lenses are still automatically created by the @Lenses annotation for data classes, but a companion object must be declared even though it might be left empty. The new processor now supports generic data classes as a new feature.

    The API for accessing a lens has changed:

    • there is no L-object holding all lenses for all domain types anymore
    • the lens is now created by a factory function instead by a property

    For accessing a lens, consider the following example:

    // somewhere in commonMain
    @Lenses
    data class Language(
        val name: String,
        val supportsFP: Boolean
    ) {
        companion object // important to declare - KSP can't create this
    }
    
    // accessing code - could also be in jsMain
    val language = storeOf(Language("Kotlin", true))
    // old: val name = kotlin.sub(L.Language.name)
    val name = language.sub(Language.name())
    

    The lenses are created as extension functions of the companion object, so no dedicated object is needed. We believe this to be more comfortable to work with: When using a lens, the domain type is already present, so this should be intuitive. The L-object wasn't too complex either, but it seemed a bit "magical" to new users, and a bit artificially named.

    To get the lens, just call the function named exactly like the corresponding property.

    This introduces one restriction to the design of a custom implemented companion object: You are not allowed to implement such a function yourself. The name is required by the processor and defiance will lead to an expressive compilation error.

    Migration guide

    Tweak buildfile

    The following changes must be applied to the build.gradle.kts

    plugins {
        // Kotlin 1.6.x version
        kotlin("multiplatform") version "1.6.10"
        // Add KSP support
        id("com.google.devtools.ksp") version "1.6.10-1.0.2"
        // Remove fritz2-plugin
    }
    
    // Add further settings for KSP support:
    dependencies {
        add("kspMetadata", "dev.fritz2:lenses-annotation-processor:$fritz2Version")
    }
    kotlin.sourceSets.commonMain { kotlin.srcDir("build/generated/ksp/commonMain/kotlin") }
    tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().all {
        if (name != "kspKotlinMetadata") dependsOn("kspKotlinMetadata")
    }
    // Needed to work on Apple Silicon. Should be fixed by 1.6.20 (https://youtrack.jetbrains.com/issue/KT-49109#focus=Comments-27-5259190.0-0)
    rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
        rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().nodeVersion = "16.0.0"
    }
    
    Code Migration

    Migrating the code can be done quite easily and reliably with simple regular expressions via search and replace:

    • remove all imports of the L-object:
      • search: ^import .*\.L$
      • replace: by nothing
    • change invocation of the lens:
      • search: L\.([\w\.]+) (with activated case sensitivity!)
      • replace: $1\(\)

    There is one trap you don't want to step in when replacing via regexp: If you have a receiver that is or ends with a big "L", this will be mistakenly removed:

    class Foo<L> {
        fun L.doSomething() = ... // The regexp will change this line as well
    }
    

    There could be more false positives we have not encountered yet, so watch out for compiler errors after applying those regexps.

    Further Cross-functional Constraints

    • fritz2 now requires Kotlin 1.6.10 in order to run
    • fritz2 now supports all Java versions again (up to 17 right now)

    PR #543: Router is now a Store

    Internally, a Router is now a Store, so you can use everything a Store offers and create your own Handlers for your Router instance (compared to RootStore):

    object MyRouter : MapRouter(mapOf("page" to "overview")) {
    
        val overview = handle {
            it + ("page" to "overview")
        }
    
        val details = handle<String> { route, id ->
            route + mapOf("page" to "details", "detailsId" to id)
        }
    }
    
    
    // Navigate to overview page
    clicks handledBy MyRouter.overview
    // Navigate to details page with detailsId=12
    clicks.map { "12" } handledBy MyRouter.details
    

    Smaller API breaking changes

    • PR #560: Removed deprecated uniqueId() function → use Id.next() instead

    New Features

    PR #556: Improve Shortcut Handling of KeyboardEvents

    This PR improves the handling of shortcuts when dealing with KeyboardEvents:

    • remove Key-class
    • add improved Key-class named Shortcut based upon a new concept of Modifier-interface which enables constructing shortcuts, e.g. "Strg + K" or "Shift + Alt + F".
    • add improved Keys-object with predefined common keys like Tab, Enter, also add the modifier keys like Alt, Shift, Meta and Control
    • mark the special interest functions key() as deprecated in favor of relying on standard flow functions like filter or map.

    The new shortcut API allows easy combination of shortcuts with modifier shortcuts, constructing those from a KeyboardEvent, and also prevents meaningless combinations of different shortcuts:

    // Constructing a shortcut by hand
    Shortcut("K") 
    // -> Shortcut(key = "K", ctrl = false, alt = false, shift = false, meta = false)
    
    // Or use factory function:
    shortcutOf("K")
    
    // Set modifier states, need to use constructor:
    Shortcut("K", ctrl = true) // Shortcut(key= "K", ctrl = true, alt = false, shift = false, meta = false)
    
    // Constructing a shortcut from a KeyboardEvent
    div {
        keydowns.map { shortcutOf(it) } handledBy { /* use shortcut-object for further processing */ }
        //                        ^^
        //                        use KeyboardEvent to construct a Shortycut-object with all potentially 
        //                        modifier key states reflected!
    }
    
    // Using predefined shortcuts from Keys object
    Keys.Enter // named-key for the enter key stroke, is a `Shortcut`
    Keys.Alt // `ModifierShortcut` -> needs to be combined with a "real" shortcut in order to use it for further processing
    // The same but more cumbersome and prone to typos
    Shortcut("Enter")
    // Not the same (!)
    Shortcut("Alt") // -> Shortcut(key= "Alt", ..., alt = false)
    Keys.Alt // -> ModifierKey-object with alt = true property!
    
    // Constructing a shortcut with some modifier shortcuts
    Shortcut("K") + Keys.Control
    // Same result, but much more readable the other way round:
    Keys.Control + "K"
    
    // Defining some common combination:
    val searchKey = Keys.Control + Keys.Shift + "F"
    //              ^^^^^^^^^^^^
    //              You can start with a modifier shortcut.
    //              Appending a String to a ModifierKey will finally lead to a `Shortcut`.
    
    val tabbing = setOf(Keys.Tab, Keys.Shift + Keys.Tab)
    
    // API prevents accidently usage: WON'T COMPILE because real shortcuts can't be combined
    Shortcut("F") + Shortcut("P") 
    
    // Shortcut is a data class → equality is total:
    Keys.Control + Keys.Shift + "K" == Shortcut("K", shift = true, ctrl= true, alt = false, meta = false)
    // But
    Keys.Control + Keys.Shift + "K" != Shortcut("K", shift = false, ctrl= true, alt = false, meta = false)
    //             ^^^^^^^^^^                        ^^^^^^^^^^^^^
    //                 +-----------------------------------+
    
    // Case sensitive, too. Further impact is explained in next section.
    shortcutOf("k") != shortcutOf("K")
    

    Be aware of the fact that the key-property is taken from the event as it is. This is important for all upper case keys: The browser will always send an event with shift-property set to true, so in order to match it, you must construct the matching shortcut with the Shift-Modifier:

    // Goal: Match upper case "K" (or to be more precise: "Shift + K")
    keydowns.events.filter { shortcutOf(it) == shortcutOf("K") } handledBy { /* ... */ }
    //                       ^^^^^^^^^^^^^^    ^^^^^^^^^^^^^^^
    //                       |                 Shortcut(key = "K", shift = false, ...)
    //                       |                                     ^^^^^^^^^^^^
    //                       |                                 +-> will never match the event based shortcut!
    //                       |                                 |   the modifier for shift needs to be added!
    //                       Shortcut("K", shift = true, ...)--+
    //                       upper case "K" is (almost) always send with enabled shift modifier!
    
    // Working example
    keydowns.events.filter { shortcutOf(it) == Keys.Shift + "K" } handledBy { /* ... */ }
    

    Since most of the time you will be using the keys within the handling of some KeyboardEvent, there are some common patterns relying on the standard Flow functions like filter, map or mapNotNull to apply:

    // All examples are located within some Tag<*> or WithDomNode<*>
    
    // Pattern #1: Only execute on specific shortcut:
    keydowns.events.filter { shortcutOf(it) == Keys.Shift + "K"}.map { /* further processing if needed */ } handledBy { /* ... */ }
    
    // Variant of #1: Only execute the same for a set of shortcuts:
    keydowns.events.filter { setOf(Keys.Enter, Keys.Space).contains(shortcutOf(it)) }.map { /* further processing if needed */ } handledBy { /* ... */ }
    
    // Pattern #2: Handle a group of shortcuts with similar tasks (navigation for example)
    keydowns.events.mapNotNull{ event -> // better name "it" in order to reuse it
        when (shortcutOf(event)) {
            Keys.ArrowDown -> // create / modify something to be handled
            Keys.ArrowUp -> // 
            Keys.Home -> // 
            Keys.End -> // 
            else -> null // all other key presses should be ignored, so return null to stop flow processing!
        }.also { if(it != null) event.preventDefault() // page itself should not scroll up or down! }
        //          ^^^^^^^^^^
        //          Only if a shortcut was matched
    } handledBy { /* ... */ }
    

    (The final result is based upon the PR #565 too)

    PR #558: Add Middleware support to html API + pre-build stateless Authentication middleware

    In the fritz2 http API you can now add Middlewares which have the following definition:

    interface Middleware {
        suspend fun enrichRequest(request: Request): Request
        suspend fun handleResponse(response: Response): Response
    }
    

    You can add a Middleware to all your http calls by using the new use(middleware: Middleware) function.

    val logging = object : Middleware {
        override suspend fun enrichRequest(request: Request): Request {
            console.log("Doing request: $request")
            return request
        }
    
        override suspend fun handleResponse(response: Response): Response {
            console.log("Getting response: $response")
            return response
        }
    }
    
    val myAPI = http("/myAPI").use(logging)
    ...
    

    You can add multiple Middlewares in one row with .use(mw1, mw2, mw3). The enrichRequest functions will be called from left to right (mw1, mw2, mw3), the handleResponse functions from right to left (mw3, mw2, mw1). You can stop the processing of a Middleware's Response further down the chain with return response.stopPropagation().

    Also, we built a pre-implemented Authentication middleware to enrich all request with authentication information and handle bad authentication responses in general:

    abstract class Authentication<P> : Middleware {
    
        // List of status codes forcing authentication
        open val statusCodesEnforcingAuthentication: List<Int> = listOf(401, 403)
    
        // Add your authentication information to all your request (e.g. append header value) 
        abstract fun addAuthentication(request: Request, principal: P?): Request
    
        // Start your authentication process (e.g. open up a login modal)
        abstract fun authenticate()
    
        val authenticated: Flow<Boolean>
       
        val principal: Flow<P?>
    }
    

    For more information have a look at the docs.

    PR #544: Rework RenderContext

    The nearest MountPoint is now available on scope when rendering (some convenience methods to access it). It allows the registration of lifecycle-handlers after mounting and before unmounting a Tag from the DOM:

    div {
        afterMount(someOptionalPayload) { tag, payload ->
            // Do something here
        }
    
        beforeUnmount(someOptionalPayload) { tag, payload ->
            // Do something here
        }   
    }
    

    This new feature is used for the transition support (see next item).

    PR #545: Transitions (css-animations on DOM manipulation)

    This PR adds transition-support to fritz2. You now can define a css-transition by the css classes describing the transition itself as well as the start- and endpoint like..

    // CSS is tailwindcss (https://tailwindcss.com)
    val fade = Transition(
        enter = "transition-all duration-1000", 
        enterStart = "opacity-0",
        enterEnd = "opacity-100",
        leave = "transition-all ease-out duration-1000",
        leaveStart = "opacity-100",
        leaveEnd = "opacity-0"
    )
    

    ..and apply it to a tag:

    div {
        inlineStyle("margin-top: 10px; width: 200px; height: 200px; background-color: red;").   
        transition(fade)
    }
    

    Recommendation: Do not apply transitions to tags that are also styled dynamically (Flow based). This might introduce race conditions and therefore unwanted visual effects.

    Improvements

    • PR #564: Provide annex context for creating elements as direct sibling of a tag

    Fixed Bugs

    • PR #557: Removed touchends-event scrolling from appFrame component (fixed jumping screen problem)
    • PR #546: Use a SharedFlow for Web Component attribute changes (Many thanks to @tradeJmark for his great work!)
    Source code(tar.gz)
    Source code(zip)
  • v0.13(Oct 21, 2021)

    Breaking Changes

    PR #530: Alternative approach to rendering

    Motivation

    • There were four bugs that dealt with rendering problems, so obviously the current solution was not good / mature enough yet:
      • #531
      • #405
      • #346
      • #538
    • The code was rather complex
    • The mechanism itself was rather cumbersome, as there was some placeholder tag rendered into the DOM, which would be replaced by the actual flow evaluated DOM fragement.

    All this was only necessary to enable to mix mount-points with static DOM tags below the same parent node.

    → Optimization only for one edge case! → not a good idea!

    New Solution

    The new rendering is based upon mount-points, that are always represented by a dedicated tag within the DOM tree! The dynamic content is then rendered below this mount-point-tag. This is true for all render variations, so for render as well as for renderEach variants.

    To be more precise, there is one div-tag inserted at the location where the render method is called:

    // within some RenderContext
    section {
        flowOf("Hello, World!").render {
            span { +it }
        }
    }
    

    This will result in the following DOM structure:

    <section>
      <div class="mount-point" data-mount-point>
        <span>Hello World</span>
      </div>
    </section>
    

    The CSS class mount-point makes the div "invisible" to the client (by display="contents"), the data attribute data-mount-point is primarely added to support readability or debugging.

    It is worth to emphasize, that this mount-point-tag remains under full control of the framework. So all rendered tags below this tag, will be cleared out every time a new value appears on the flow. So do not try to use or touch this tag or any child from outside of the render function!

    This works similar for dynamic lists:

    ul {
        flowOf(listOf("fritz2", "react", "vue", "angular")).renderEach {
            li { +it }
        }
    }
    

    Which will result in this DOM structure:

    <ul>
      <div class="mount-point" data-mount-point>
        <li>fritz2</li>
        <li>react</li>
        <li>vue</li>
        <li>angular</li>
      </div>
    </ul>
    
    Recipe: Mixing dynamic and static content within the same level

    If it is absolutely clear that the mount-point will be the only element of some parent tag, then the render methods offer the optional into parameter, which accepts an existing RenderContext as anchor for the mount-point. In this case the rendering engine uses the existing parent node as reference for the mount-point:

    render {
        ul { // `this` is <ul>-tag within this scope
            flowOf(listOf("fritz2", "react", "vue", "angular")).renderEach(into = this) {
            //                                                             ^^^^^^^^^^^
            //                                                             define parent node as anchor for mounting    
                li { +it }
            }
        }
    }
    

    This will result in the following DOM structure:

    <ul data-mount-point> <!-- No more explicit <div> needed! Data attribute gives hint that tag is a mount-point -->
      <li>fritz2</li>
      <li>react</li>
      <li>vue</li>
      <li>angular</li>
    </ul>
    

    If you are in a situation where you absolutly have to mix static elements with dynamic (flow based) content within the same DOM level, then the new rendering offers a solution too: Try to integrate the static aspect within a map expression!

    Let's consider the following example sketch we would like to achieve:

    <ul>
      <!-- static elements within the list items, always on top -->
      <li>fritz2</li>
      <!-- dynamic content from a flow -->
      <li>react</li>
      <li>vue</li>
      <li>angular</li>
    </ul>
    

    The simplest solution would be to just call the renderEach method directly within the <ul> context after the static <li> portions. But this would violate the constraint, that all <li> tags must appear on the same DOM level (refer to the first example output to see, that an extra <div> would be insterted after the static portion).

    So the correct way is to provide the into = this parameter in order to lift up the dynamic portion into the surrounding <ul> tag and to integrate the static portion within the flow by some map expression:

    val frameworks = flowOf(listOf("react", "vue", "angular")) // might be a store in real world applications
    ul {
        frameworks
            .map { listOf("fritz2") + it } // prepend the static part to the dynamic list
            .renderEach(into = this) { // do all the rendering in the "dynamic" part of the code
                li { +it }
            }
    }
    

    The result is exactly the same as the scetch from above.

    You might habe recognized that the into parameter could be omitted, if the extra <div> does not affect the overall structure (in this case all <li> elements would still remain on the same level within the DOM!).

    Migration Guide

    For the most if not all parts of your application nothing has to be changed!

    So first of all please compile and test your application. If there are no compiler errors and the application appears and functions as it did before, do nothing!

    There are only few exceptions to this rule:

    1. renderElement does not exist anymore. You will get compile errors of course, so this is easy to detect. Change all occurrences to render instead to solve this problem. If needed, apply the next two patterns on top.
    2. If the additional mount-point-tag leads to unwanted effects (some styling applied to children won't work for example), just provide the parent tag as mount-point-tag by setting the into = this parameter at the render functions calls.
    3. If there are parts of your application where dynamic and static content absolutely needs to coexist within the same DOM level, strive to include the static portion into the flow (for example by using map as shown in the recipe section before).
    4. If you use our StackUp or LineUp component with dynamic content, make sure to set the into = this parameter in order to make the spacing property work again.

    Further Information

    For more details have a look at the documentation

    New Features

    PR #532: Adding new Adhoc-Handler

    Added new easy to use handledBy functions which have a lambda parameter. Anytime a new value on the Flow occurs the given function gets called.

    render {
       button {
           +"Click me!"
           clicks handledBy {
               window.alert("Clicked!")
           }
       }
    }
    

    Outside the HTML DSL you can do the same:

    flowOf("Hello World!") handledBy {
        window.alert(it)
    }
    

    That is why the watch() function at the end of a Flow is not needed anymore and is now deprecated.

    // before    
    flowOf("Hello World!").onEach {
        window.alert(it)
    }.watch()
        
    // now
    flowOf("Hello World!") handledBy {
        window.alert(it)
    }
    

    This way the handling of data in fritz2 (using Flowss) gets more consistent.

    Be aware that per default every Throwable is caught by those handledBy functions and a message is printed to the console. The flow gets terminated then, so no more possibly following values will be processed:

    flowOf("A", "B", "C").map {
        delay(2000)
        it
    } handledBy {
        if (it == "B") error("error in B") // provoke an exception
        window.alert(it)
    }
    // will open one window with "A" and then print a message top the console:
    // Object { message: "error in B", cause: undefined, name: "IllegalStateException", stack: "captureStack@webpack-internal:...
    

    The application itself will continue to work, which is the main motivation for the enforced default error handling though!

    We encourage you to handle possible exceptions explicitly within the handler code, so the flow will keep on working; at least for the following valid values:

    flowOf("A", "B", "C").map {
        delay(2000)
        it
    } handledBy {
        try {
            if (it == "B") error("error in B")
        } catch (e: Exception) { // handle exception within handler -> flow will be further consumed!
        }
        window.alert(it)
    }
    // will open three alert windows with all three chars "A", "B" and "C" as expected
    

    PR #529: Add merge function to merge multiple DomListeners

    This convenience functions reduces duplicate code for handling different DomListeners with the same handler:

    button {
        merge(mouseenters, focuss) handledBy sameHandler
    }
    

    PR #528: Add dynamic icons for sub-menus

    This PR enhances the API of the submenu component's icon defintion. Instead of just apssing some static IconDefinition it is now possible to pass some Flow<IconDefinition>too. This enables one to realize an "accordeon style" menu, where the icon reflects the open / close state of the submenu:

    // some store to hold the open/close state of a submenu
    val toggle = storeOf(false)
    
    menu {
        header("Entries")
        entry {
            text("Basic entry")
        }
        submenu(value=toggle) {
            text("Sub")
            // use the state to select the appropriate sub-menu icon
            icon(toggle.data.map { if (it) Theme().icons.chevronDown else Theme().icons.chevronLeft })
            entry {
                text("A")
            }
            entry {
                text("B")
            }
            entry {
                text("C")
            }
        }
    }
    

    2021-09-28 14_59_24-KitchenSink - fritz2 components demo

    PR #522: Expose open/close handlers or an external store for submenu component

    PR #519: Add often used attributes for svg and path element

    It is now possible to craft SVG images much easier within fritz2, its element DSL now supports the <path>-Tag and the following attributes:

    • xmlns
    • fill
    • viewBox
    svg {
        // quite common for each SVG declaration, so directly supported
        xmlns("http://www.w3.org/2000/svg")
        fill("none")
        viewBox("0 0 24 24")
        // too special, so no explicit wrapping function provided
        custom("circle", "opacity-25") {
            attr("cx", "12")
            attr("cy", "12")
            attr("r", "10")
            attr("stroke", "currentColor")
            attr("stroke-width", "4")
        }
        path {
            attr("fill", "currentColor")
            // `d` is explictily supported by `path` tag
            d("M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z")
        }
    }
    

    Improvements

    • PR #535: Deprecate randomId function from dev.fritz2.components.foundations package
    • PR #525: Improve null value handling for attribute setting
    • PR #539: Remove obsolete watch calls

    Fixed Bugs

    • PR #524: Fixed baseClass at Svg and Path
    • PR #523: Fixed not working disabled property in formControl component
    • PR #521: Handle possible ClassCastException
    • Issue #538: Popups do not work in Modals
    Source code(tar.gz)
    Source code(zip)
  • v0.12(Aug 31, 2021)

    Breaking Changes

    PR #494: Allow sub() on all Stores

    Removes the RootStore dependency in the SubStore, so that SubStores can be created by calling sub() function implemented in Store interface. Therefore it is now possible to create a SubStore from every Store object.

    Because one SubStore generic type gets obsolete, it will break existing code, but it is easy to fix that. Just remove the first not type parameter.

    // before
    class SubStore<R, P, T> {}
    // now
    class SubStore<P, T> {}
    
    // before
    val addressSub: SubStore<Person, Person, Address> = store.sub(addressLens)
    // now
    val addressSub: SubStore<Person, Address> = store.sub(addressLens)
    

    PR #492: Remove deprecated direction-api from RadioGroupComponent and CheckboxGroupComponent

    This PR removes the deprecated direction-api from RadioGroupComponent and CheckboxGroupComponent. Use orientation instead.

    PR #489: Some Improvements and Bugfixing around Forms

    Improve FormControl code

    • make FormSizeSpecifier values uppercase, so more Kotlin alike
    • improve render strategies initialization and customization: Instead of setting all up within the init block, there is now a new private method initRenderStrategies that produces the mapping and is directly called at beginning of the rendering process. This way no this pointer is leaked as before. For custom implementations there is now a protected hook function finalizeRenderStrategies that can be used to extend or change the renderer strategies!
    Migration Guide:

    If a custom renderer should be applied or a new factory should be added, custom implementation of FormControlComponent must replace the old registerRenderStrategy within the init block by a new mechanism:

    // old way, no more possible!
    class MyFormControlComponent : FormControlComponent {
        init {
            registerRenderStrategy("radioGroupWithInput", ControlGroupRenderer(this))
        }
    }
    
    // new way, override `finalizeRenderStrategies` method:
    class MyFormControlComponent : FormControlComponent {
    
        // some new factory: `creditCardInput` with key of same name; used with `SingleControlRenderer`
    
        // another new factory: `colorInput` with key of same name and new renderer
    
        override fun finalizeRenderStrategies(
            strategies: MutableMap<String, ControlRenderer>,
            single: ControlRenderer,
            group: ControlRenderer
         ) {
             // override setup for a built-in factory:
             strategies.put(ControlNames.textArea, MySpecialRendererForTextAreas(this))
    
             // register new factory
             strategies.put("creditCardInput", single)
    
             // register new factory with new renderer
             strategies.put("colorInput", ColorInputRenderer(this))
         }
    }
    

    Improve sizes aspect for components in theme

    • in respect to EfC 502 a new mixin alike interface FormSizesAware has been introduced
    • the FormSizes interface is renamed to FormSizesStyles
    Migration Guide:

    If a component supports a size property and relies on the old FormSizes interface, just rename the receiver type appropriate to `FormSizesStyles``:

    // old
    val size = ComponentProperty<FormSizes.() -> Style<BasicParams>> { Theme().someComponent.sizes.normal }
    
    // new
    val size = ComponentProperty<FormSizesStyles.() -> Style<BasicParams>> { Theme().someComponent.sizes.normal }
    //                           ^^^^^^^^^^^^^^^
    //                           choose new name
    

    Make FormControl Labels dynamic

    • the label property of a FormControl now accepts also Flow<String>. Thus the label can dynamically react to some other state changing.
    Migration Guide

    The code for a ControlRenderer implementation needs to be adapted. Use the following recipe:

    class ControlGroupRenderer(private val component: FormControlComponent) : ControlRenderer {
        override fun render(/*...*/) {
            // somewhere the label gets rendered (label / legend or alike)
            label {
                // old:
                +component.label.value
                // change to:
                component.label.values.asText()
            }
        }
    }
    

    Adapt FormControl's textArea function parameters

    • the optional store parameter now also is named value like as within the main textArea factory.

    Repairs label behaviour for SelectField

    • a click onto a label within a FormControl now directs to the SelectField.
    • the id of a SelectField is now correctly set, so that a FormControl can now fill the for attribute of the label correctly.

    PR #490: Move Modal’s close handler into content property

    Until now the close handler was injected directly into the main configuration scope of a modal. With repsect to EfC 416 this is now changed. The handler only gets injected into the content property, where it is considered to be used.

    Migration

    Just move the handler parameter from the build expression of modal into the content property:

    // old
    clickButton {
        text("Custom Close Button")
    } handledBy modal { close -> // injected at top level
        content { 
            clickButton {icon { logOut } } handledBy close
        }
    }
    
    // new
    clickButton {
        text("Custom Close Button")
    } handledBy modal {
        content { close -> // injected only within content
            clickButton {icon { logOut } } handledBy close
        }
    }
    

    PR #487: Rework ToastComponent

    Changes

    This PR cleans up the ToastComponent code. This includes:

    • Adding styling-options to the theme
    • Use correct z-indizes
    • General code-cleanup

    What's API breaking?

    • Theme().toast.base has been renamed to Theme().toast.body and might break themes that have custom toast-styles

    New Features

    PR #505: Add TooltipComponent

    This PR deals with the new TooltipComponent

    A tooltip should be used to display fast information for the user. The individual text will be shown on hover the RenderContext in which be called.

    This class offers the following configuration features:

    • text can be a vararg, a flow, a list, a flow of list of String or a simple string, optional can be use the @property textFromParam.
    • placement of the text around the RenderContextin which be called. Available placements are top, topStart, topEnd, bottom, bottomStart, bottomEnd, left, leftStart, leftEnd, right, rightStart, rightEnd.

    Example usage:

    span {
       +"hover me"
       tooltip("my Tooltip on right side") {
          placement { right }
        }
    }
     
    span {
       +"hover me to see a multiline tooltip"
       tooltip("first line", "second line"){}
    }
    
    span {
       +"hover me for custom colored tooltip"
       tooltip({
            color { danger.mainContrast }
            background {
                color { danger.main }
            }
       }) {
            text(listOf("first line", "second line"))
            placement { TooltipComponent.PlacementContext.bottomEnd }
        }
    }
    

    Migration

    • The old tooltip component remains as deprecated for the next releases, so clients have time to migrate.

    Motivation

    The old Tooltip component is based upon pure CSS. This is fine for basic usage, but it fails when it comes to advanced features like automatic positioning. Also the API does not fit into the "fritz2 component style". That's why there is the need to rework the tooltip.

    PR #493: Add PopupComponent

    The PopupComponent should be used for to positioning content like tooltip or popover automatically in the right place near a trigger.

    A popup mainly consists of a trigger (the Element(s)) which calls the content . It can cen configured by

    • offset the space (in px) between trigger and content
    • flipping if no space on chosen available it will be find a right placement automatically
    • placement of the content around the trigger

    The trigger provides two handler which can be used, the first is important to open/toggle the content the second close it. content provides one handler which can be used to close it.

    Example:

    popup {
        offset(10.0)
        flipping(false)
        placement { topStart }
        trigger { toggle, close ->
            span {
                +"hover me"
                mouseenters.map { it.currentTarget } handledBy toggle
                mouseleaves.map { } handledBy close
            }
        }
        content { close ->
            div {
                +"my content"
                clicks.map{ } handledBy close
            }
        }
    }
    

    PR #496: Add PaperComponent and CardComponent

    This PR adds a new CardComponent that behaves similar to the old PopoverComponents content.

    Paper Component

    Component displaying content in a card-like box that can either appear elevated or outlined and scales with the specified size of the component.

    Example
    paper {
        size { /* small | normal | large */ }
        type { /* normal | outline | ghost */ }
    
        content {
            // ...
        }
    }
    

    Bildschirmfoto 2021-08-17 um 19 09 11

    CardComponent

    A component displaying the typical sections of a card inside a PaperComponent.
    The available sections are a header, a footer and the actual content.

    Example
    card {
        size { /* small | normal | large */ }
        type { /* normal | outline | ghost */ }
    
        header {
            // ...
        }
        content {
            // ...
        }
        footer {
            // ...
        }
    }
    

    Bildschirmfoto 2021-08-17 um 19 17 18

    PR #481: Add possibility to pass arbitrary payload data in fritz2's DSL

    In fritz2 you can now put some arbitrary payload data to every fritz2 html element (for styled elements too) and receive this payload data later (or deeper) in your html tree. This possibility can be use to know in with kind of context your own component get rendered or you can provide some additional information to your context for making decision on styling or rendering.

    Example:

    enum class Sizes {
        SMALL, NORMAL, LARGE;
    
        companion object {
            val key = keyOf<Sizes>()
        }
    }
    
    fun main() {
        render {
            div {
                div(scope = {
                    set(Sizes.key, Sizes.SMALL)
                }) {
                    section {
                        scope.asDataAttr()
                        when (scope[Sizes.key]) {
                            Sizes.SMALL -> div({ fontSize { small } }) { +"small text" }
                            Sizes.NORMAL -> div({ fontSize { normal } }) { +"normal text" }
                            Sizes.LARGE -> div({ fontSize { large } }) { +"large text" }
                            else -> div { +"no size in scope available" }
                        }
                    }
                }
                p {
                    scope.asDataAttr()
                    // scope is context-based and therefore scope is here empty
                    +"no scope entries here (context-based)"
                }
            }
        }
    }
    

    Results in: Screenshot_20210707_165401

    PR #470: Add TypeAheadComponent

    Adds a TypeAhead component to fritz2's component portfolio.

    A TypeAhead offers the possibility to input some string and get some list of proposals to choose from. This is reasonable for large static lists, that can't be managed by SelectFields or RadioGroups or where the proposals rely on a remote resource.

    Example usage:

     val proposals = listOf("Kotlin", "Scala", "Java", "OCaml", "Haskell").asProposals()
     val choice = storeOf("")
     typeAhead(value = choice, items = proposals) { }
    

    For further details have a look at our KitchenSink


    Improvements

    PR #514: Upgrade Kotlin to Version 1.5.30

    PR #507: Add TooltipMixin and TooltipProperties to use TooltipComponent in Components more easily

    With this PR it beeing easier to integrate a tooltip in a component and the user get a same usage of a tooltip.

    PR #486: Use colors in appFrame and added submenu in menu component

    The appFrame component uses now different ColorSchemes to coloring itself.

    The menu component can now contain submenus:

    menu {
        header("Entries")
        entry {
            text("Basic entry")
        }
        divider()
        custom {
            pushButton {
                text("I'm a custom entry")
            }
        }
        submenu {
            icon { menu }
            text("Sub Menu")
            entry {
                icon { sun }
                text("Entry with icon")
            }
            entry {
                icon { ban }
                text("Disabled entry")
                disabled(true)
            }
        }
    }
    

    PR #483: Deprecate box in favor of div

    This PR deprecates the box factory method in favor of div because, in combination with fritz2.styling, it offers the exact same functionality. All calls of box will have to be replaced eventually.

    PR #477: Add convenience functions to create toasts with alert-content

    This PR adds two convenience functions alertToast and showAlertToast which can be used to easily create toasts with alert's as their content.

    New functions:

    fun showAlertToast(
        styling: BasicParams.() -> Unit = {},
        baseClass: StyleClass = StyleClass.None,
        id: String? = null,
        prefix: String = "toast-alert",
        build: AlertComponent.() -> Unit
    )
    
    fun alertToast(
        styling: BasicParams.() -> Unit = {},
        baseClass: StyleClass = StyleClass.None,
        id: String? = null,
        prefix: String = "toast-alert",
        build: AlertComponent.() -> Unit
    ): SimpleHandler<Unit>
    

    Example usage:

    showAlertToast(buildToast = {
       
    }) { 
        // toast-properties:
        duration(6000)
    
        // setup of the alert
        alert {
            title("Alert-Toast")
            content("Alert in a toast")
            severity { /* some severity */ }
            variant { leftAccent }
        }
    }
    
    // 'alertToast' is used similarly and bound to a flow  like 'toast'
    
    Previous usage
    val alertComponent = AlertComponent()
        .apply {
            content("...")
            stacking { toast }
        }
    
    showToast {
        // adjust the close-button to match the alert's color-scheme:
        closeButtonStyle(Theme().toast.closeButton.close + {
            color {
                val colorScheme = alertComponent.severity.value(Theme().alert.severities).colorScheme
                when(alertComponent.variant.value(AlertComponent.VariantContext)) {
                    AlertComponent.AlertVariant.SUBTLE -> colorScheme.main
                    AlertComponent.AlertVariant.TOP_ACCENT -> colorScheme.main
                    AlertComponent.AlertVariant.LEFT_ACCENT -> colorScheme.main
                    else -> colorScheme.mainContrast
                }
            }
        })
        content {
            alertComponent.render(this, styling, baseClass, id, prefix)
        }
    }
    

    Result: Bildschirmfoto 2021-07-06 um 10 35 19

    PR #462: Rename alert variant discreet to ghost

    This PR renames the discreet alert-variant to ghost in order to match the common naming scheme of fritz2.

    All references of discreet have to be changed:

    alert {
        variant { discreet } // <-- previously
        variant { ghost } // <-- now
    }
    

    Fixed Bugs

    • PR #515: Handle possible CastException
    • PR #511: fix appearance of inputs, textareas and select on iOS
    • PR #502: fix type of InputEvent
    • PR #500: Fix layout issues in radio, checkbox and switch components
    • PR #471: Fix close-button margin in ToastComponent
    Source code(tar.gz)
    Source code(zip)
  • v0.11.1(Jun 22, 2021)

    Improvements

    PR #443: Use any element/component as a label in radio- and checkbox-groups

    This PR adds the option to use any element or component for the label of the items in a radio- or checkbox-group. Previously it was only possible to generate Strings from the underlying objects by using the label property:

    val label = ComponentProperty<(item: T) -> String> { it.toString() }
    

    Both components now feature a dedicated labelRendering property that can be used to override the default rendering process and specify custom layouts.
    The labelRendering property is a lambda that takes each underlying item: T and renders the respective label.
    By default the label is rendered based on the label property.

    // RadioGroupComponent:
    val labelRendering = ComponentProperty<Div.(item: T) -> Unit> { /* rendering based on 'label' by default */ }
    
    // CheckboxGroupComponent:
    val labelRendering = ComponentProperty<RenderContext.(item: T) -> Unit> { /* rendering based on 'label' by default */ }
    

    Usage example:

    checkboxGroup(values = /* ... */, items = /* ... */) {
        labelRendering { item ->
            span({
                // styling
            }) {
                +item
            }
        }
    }
    

    PR #435: Added new reset() function to Validator

    Now you can easily clean the list of validation messages by calling the new reset() function. If you want you can specific a list of validation messages to reset to.

    val save = handleAndEmit<Person> { person ->
        // only update the list when new person is valid
        if (validator.isValid(person, "add")) {
            emit(person)
            validator.reset() // new reset function
            Person()
        } else person
    }
    

    PR #442: Rework modal

    This PR improves the following aspects of a modal:

    • scrolling behaviour: Per default there are some settings to enable scrolling automatically without any client side effort.
    • width settings (API change): new name width instead of size to make the intention of the property more expressive. The predefined values remains the same, but it is also possible now to provide a custom width value like 30rem or alike. The old API calls will be applied and result in the same visual apperance for now.
    • vertical placement (API change): This replaces the old variant property, which won't lead into an error, but the functionality is disabled. Instead use the placement property to align a modal vartically: top (default), center, bottom and stretch are the possible values.

    Example:

    clickButton {
        text("Open")
    } handledBy modal({
        minHeight { "30rem" }
    }) {
        width { "800px" }
        placement { center }
        content {
            // some content
        }
    }
    

    The old API calls for size and variant don't break your code, but they will be removed in future versions. So please follow the mrigration guide to recplace them.

    Be aware that also the interfaces ModalVariants and ModalSizes from the theme are deprecated and will disappear in future versions.

    For a complete overview have a look at our KitchenSink.

    Migration advices:

    size to width:

    modal {
        // old:
        // size { small }
    
        // new
        width { small }
    }
    

    variant to placement:

    • autotop
    • verticalFilledstretch
    • centeredcenter
    modal {
        // old:
        // variant { verticalFilled }
    
        // new
        placement { stretch }
    }
    

    Theming migration:

    • Define new predefined values in ModalWidths with the values taken from ModalSizes.
    • ModalVariants just disappears. There is no more theme element that replaces this one. For probably adavanced settings applied here, integrate those into ModalStyles properties base, width and internalScrolling.

    PR #436: Rework popover component

    • remove unnecessary getElementById() call
    • fix id and prefix placement in DOM (analog to dropdown)
    • fix and rearrange kdoc
    • refactor & reformat code

    Further improvements

    • PR #452: Add formGroup component

    Fixed Bugs

    • PR #434: Use explicit type specification for Icons in DefaultTheme
    • PR #430: Make track() function of tracker safe for unsafe operations
    • PR #451: Fixed slider styling with delegations
    • PR #458: Added missing package line in select.kt and nav.kt
    Source code(tar.gz)
    Source code(zip)
  • v0.11(Jun 8, 2021)

    Breaking Changes

    PR #409: Upgrade to Kotlin 1.5

    Upgrading to Kotlin 1.5.10 version and update dependencies to latest version.

    PR #420: Using ComponentProperty for setting an IconDefinition for a button

    This API change for pushButton, clickButton and linkButton harmonizes the way for setting an icon to it.

    // before
    pushButton {
        type { info }
        icon { fromTheme { circleInformation } }
        text("Info")
    }
    
    // now
    pushButton {
        type { info }
        icon { circleInformation }
        text("Info")
    }
    

    In most cases it is enough to remove the fromTheme() function.

    PR #415: Use constant values for z-indices in components

    Components which need a z-index an their default level values defined in DefaultTheme:

    tableHeader (10)
    tooltip (100)
    dropdown (200)
    popover (300)
    appFrame (1000)
    navbar (1000) -- will be replaced by appFrame
    toast (2000)
    modal (3000)
    

    New Features

    PR #326 and #424: Add Menu and Dropdown components

    Menu

    New component to build menus consisting of clickable entries, headers, dividers and other items.

    menu {
        header("Menu")
        entry {
            text("Clickable item")
            icon { notification }
            disabled(false)
            events {
                // standard events context
            }
        }
        divider()
        header("Other")
        custom {
            pushButton {
                text("I'm a custom entry")
            }
        }
    }
    

    Dropdown

    New component to display dropdown-content floating around a toggle-element, similar to a popover.

    dropdown {
        toggle {
            pushButton {
                text("Toggle")
            }
        }
        placement { bottom }
        alignment { start }
        content {
            // ...
        }
    }
    

    Improvements

    PR #417: Harmonize orientation settings

    • add new general concepts for the orientation aspect of a component: That is align its item(s) in an horizontal or vertical way.
    • offer a new mixin OrientationMixin for components
    • add this mixin for the following components:
      • Slider (no API change!)
      • CheckBoxGroup: new orientation API, old direction marked as deprecated
      • RadioGroup: new orientation API, old direction marked as deprecated

    Migration

    • directionorientation
    • rowhorizontal
    • columnvertical
    // old one with deprecation warning as:
    // 'direction: ComponentProperty<CheckboxGroupComponent.DirectionContext.() -> CheckboxGroupComponent.Direction>' is deprecated. Use orientation instead
    // 'row: CheckboxGroupComponent.Direction' is deprecated. Use orientation { horizontal } instead
    checkboxGroup(values = someStore, items = someItems) {
        direction { row }
    }
    
    // change like this
    checkboxGroup(values = someStore, items = someItems) {
        orientation { horizontal }
    }
    

    Fixed Bugs

    • PR #423: Removed duplicated html elements which are causing errors
    • PR #422: Fixed self type loss after using global functions on Listener like preventDefault etc.
    Source code(tar.gz)
    Source code(zip)
  • v0.10.1(Jun 1, 2021)

    Improvements

    • PR #380: Added new linkButton component
      linkButton {
          text("Show me awesome")
          href("www.fritz2.dev")
          target("_blank")
      }
      
    • PR #387: Add new slider component
      val valueStore = storeOf(100)
      slider(value = valueStore) {}
      
    • PR #362: Add new menu component
      menu(demoMenuStyle) {
          header("Items")
          entry {
              text("Basic item")
          }
          divider()
          entry {
              icon { sun }
              text("Item with icon")
          }
      }
      
    • PR #370: Remove misleading Inter font
    • PR #369: Added convenience methods inspectEach() for Inspector on collections
    • PR #365: Fix default ColorScheme colors
    • PR #363: Improve publishing.gradle.kts
    • PR #361: Improve alert API

    Fixed Bugs

    • PR #408: Fix problems with using media breakpoints
    • PR #385: Fix selection forwarding to external store for DataTable component
    • PR #390: Fix css error in appFrame component
    Source code(tar.gz)
    Source code(zip)
  • v0.10(Apr 27, 2021)

    Breaking Changes

    PR #334: Rework of API for Keys from KeyboardEvents

    The following things has been changed:

    • Using KeyboardEvent.key instead of deprecated KeyboardEvent.keyCode for comparison
    • Extending the list of standard key (e.g. Keys.Alt, Keys.Enter, Keys.Esc)
    • Simplify the creation of a Key by calling the constructor with the given KeyboardEvent
    • Overriden equals() method which compares two Keys by Key.key attribute.
    • The toString() returns the key attribute
    input {
        keydowns.key().map { 
            when (it) {
                Keys.Enter -> //...
                Keys.Escape -> //...
                else -> //..
            }
        } handledBy store.myHandler
    }
    

    PR #336: Fix horizontal and vertical convenience functions of BordersContext DSL

    Swapping the implementations of BorderContext.horizontal(...) and BorderContext.vertical(...) so the borders created by them follow the semantics of the respective method names.

    This means: horizontal now creates borders at the top and at the bottom of an element and vertical now creates borders to the left and right of an element.

    PR #355: Extends of ColorScheme approach

    • ColorScheme got a function named inverted() it returns a ColorScheme which switches base with highlight and baseContrast with highlightContrast. A common use case might be a creation of inverted theme.
    • The colors info , success, warning, danger and neutral changed to ColorScheme
    • The property color of our button component refactors to type and based on the ColorScheme approach now. Our default theme deliveres following predefinitions: primary, secondary, info, success, warning, danger but you can use any other ColorScheme.
       // Until 0.9.x
       clickButton {
           text("danger")
           color { danger }
       }
      
       // New
       clickButton {
           text("danger")
           type { danger }
       }
       clickButton {
           text("custom")
           type { ColorScheme("#00A848", "#2D3748", "#E14F2A", "#2D3748") }
       }
      
    • You've the possibility to setup the button schemes independently of the theme ColorSchemes
      // inside your custom theme implementation
      override val button = object : PushButtonStyles {
          override val types: PushButtonTypes = object : PushButtonTypes {
              override val primary
                  get() = ColorScheme(
                      main = "#00A848",
                      mainContrast = "#2D3748",
                      highlight = "#E14F2A",
                      highlightContrast = "#2D3748"
                  )
              //...
          }
      }
      
    • inputField and textArea now use background and font color of Colors interface as default.
    • The alert component got a rework by ColorScheme too. Calling the component is the same as before, with the addition that a ColorScheme can now also be passed into it.
      alert {
          content("Severity: success")
          severity { success }
      }
      
      alert {
          content("Severity: custom")
          severity {
              ColorScheme("#00A848", "#2D3748", "#E14F2A", "#2D3748")
          }
      }
      

    PR #356: Improve API of AlertComponent and use new ColorScheme

    Improves up the AlertComponent API by allowing more flexible customization via the AlertSeverity-style of the theme. Alerts mirroring the severity of a ComponentValidationMessage can still be created via the ComponentValidationMessage.asAlert() extension method. This PR also contains a lot of code cleanup in general.

    Alerts now also utilize the new ColorScheme approach for color shades and typography.

    The actual usage of the AlertComponent does not change - the underlying styling within the theme does, however. This means if you are using a custom theme you need to update it in order to implement the altered AlertStyles interface. In case you did not do any modifications to the AlertComponent'-theme your app should continue to work as intended.

    • Updated AlertSeverity interface now includes support for the new ColorScheme class:
      interface AlertSeverity {
          val colorScheme: ColorScheme
          val icon: IconDefinition
      }
      
    • AlertVariants now contains regular styles that are no longer split into styles for the background, text, icons, etc.
      interface AlertVariants {
          val subtle: BasicParams.(AlertSeverity) -> Unit
          val solid: BasicParams.(AlertSeverity) -> Unit
          val leftAccent: BasicParams.(AlertSeverity) -> Unit
          val topAccent: BasicParams.(AlertSeverity) -> Unit
          val discreet: BasicParams.(AlertSeverity) -> Unit
      }
      
    • Removed AlertVariantStyles interface

    PR #357: Move background & font colors to Colors interface

    Move backgroundColor and fontColor from the Theme interface to the Colors interface.

    // before
    Theme().fontColor
    Theme().backgroundColor
    
    // now
    Theme().colors.font
    Theme().colors.background
    
    // or in color styling DSL
    {  
        background {
            color {
                font //or background
            }
        }
    }
    

    New Features

    PR #337: Extend styling API

    New API for using the fritz2 styling DSL on standard HTML Tags. Important: add the following import to get the new extension functions: import dev.fritz2.styling.* The currently approach gets deprecated with v0.10 and will be removed in next major version. So please migrate to the new version as shown below.

    // Until 0.9.x
    render {
        val textColor = style { color { "yellow" } }
        (::div.styled(baseClass = textColor, id = "hello", prefix = "hello") {
            background { color { "red" } }
        }) {
            +"Hello World!"
        }
    }
    
    // New
    // don't forget the import dev.fritz2.styling.* !!!
    render {
        val textColor = style { color { "yellow" } }
        div({
            background { color { "red" } }
        }, baseClass = textColor, id = "hello", prefix = "hello") {
            +"Hello World!"
        }
    }
    

    PR #350 : Add DataTable component

    The DataTable component provides a way to visualize tabular data and offers interaction for the user in order to sort by columns or select specific rows.

    The API is designed to scale well from the simplest use case of a read only table, towards a fully interactive table with live editing a cell directly within the table itself.

    The component is also very flexible and offers lots of customization possibilities like for:

    • the styling of the header or the columns
    • the styling based upon the index or the content of a row or cell
    • the sorting mechanisms (logic and UI)
    • the selection mechanism (logic and UI)

    For a detailed overview have a look at our KitchenSink project.

    Further new features

    • PR #335 Added missing resizeBehavior option both for textArea component
    • PR #341 moves basicStyles of selectField and textArea to defaultTheme

    Improvements

    PR #349 Harmonize parameter name in components with stores

    For a better readability and intelligibility the parameter store refactores to value or rather values. value is intended for components with a Store<T> and values is intended for Store<List<T>>.

    // Until 0.9.x
     inputField(store = myStore) {}
     checkboxGroup(store = myListStore, items = myItems) {}
    
    // New
    inputField(value = myStore) {}
    checkboxGroup(values = myListStore, items = myItems) {}
    

    Especially the inputField and textArea get a better intelligibility .

    // Unitl 0.9.x
     inputField(store = myStore) {} // Store variant
     inputField { value(myFlow) } // Flow variant
    
    // New
     inputField(value= myStore) {} // Store variant
     inputField { value(myFlow) } // Flow variant
    

    Further improvements

    • PR #340: Moved mono icons to an extra package next to the License.md
    • PR #345: Make staticStyle internal
    • PR #358: Polish DataTable
    • PR #359: Rework new Svg tag in core

    Fixed Bugs

    • PR #338: Fixed problem with nested renders which contains conditions
    • PR #351: Fix problem with component-specific EventContexts
    Source code(tar.gz)
    Source code(zip)
  • v0.9.2(Apr 13, 2021)

    Improvements

    • PR #317: Using websafe fonts in default theme
    • PR #331: Using DSLMarkers for Components

    Fixed Bugs

    • PR #319: Suppressing errors messages when inserting normalize.css into CSSStyleSheet

    Gradle-Plugin

    • PR #8: Fixed bug when renaming Kotlin multiplatform targets
    Source code(tar.gz)
    Source code(zip)
  • v0.9.1(Mar 16, 2021)

    Breaking Changes

    PR #313: Improvement of theme colors

    The approach to specify colors in a theme had some serious issues up to version 0.9:

    • no semantics for dealing with content's color on top of a colored area
    • no structural grouping of related colors (only by prefix like primary and primaryEffect)
    • no definition for separating the effect colors for filling areas and rendering something on the surfaces

    That's why starting from version 0.9.1 a more expressive approach regarding the semantic and structural side is introduced. As central concept the class ColorScheme encapsulates the necessary and strongly related colors as a quadrupel:

    open class ColorScheme(
        val base: ColorProperty, // background, border, ...
        val baseContrast: ColorProperty, // text, icons, ... rendered on ``base``
        val highlight: ColorProperty, // instead of base for effects like hovering and so on
        val highlightContrast: ColorProperty // text, icons ... rendered on ``highlight``
    )
    

    There is also a complete new color slot for a tertiary color scheme and we have enhanced the palette of gray to ten different predefined shades from gray50 to gray900 instead of six named ones.

    Remark: This new concept is not applied to all relevant aspects and components, yet, so there will be adaption towards this quadrupel concept in future releases!

    Migration for custom themes

    Some color properties have been removed obviously. So here is a short migration guide:

    • instead of primary and primaryEffect put those values into the new primary quadrupel:
      // old:
      override val primary = "primaryColor"
      override val primaryEffect = "primaryEffectColor"
      
      // new:
      override val primary = ColorScheme(
          base = "primaryColor", 
          baseContrast = "someNew" , 
          highlight="primaryEffectColor",
          highlightContrast = "someNew"
          )
      
    • choose the same approach for the old pair of secondary and secondaryEffect
    • change the lightestGray, lighterGray, ... properties to appropriate new gray{number} properties. Fill the gaps with fitting shades.
    • change base to neutral
    • there is nor more dark property. Choose a fitting gray{number} or probably some sub-color from primary, secondary or tertiary quadrupel.

    PR #290: Parameterless invoke function inside the Hander interface

    No import is needed by calling a unit handler directly anymore. To fix compile issues just remove the obsolete import as follows:

    import dev.fritz2.binding.invoke // just delete this
    
    // somewhere
    val handler = myStore.handle { model -> 
        // ...
        model
     }
    
    // call this ``Unit`` handler directly
    handler()
    

    New Features

    • PR #308: New convenience functions for getting current value on enter (Input & TextArea)
    • PR #293: Migration to mavenCentral

    Improvements

    • PR #310: Decouple Modal and Toast components from local RenderContext
    • PR #305: Extending fritz2 DefaultTheme
    • PR #298: Move basicInputStyles of inputField Component to default theme
    • PR #287: Using offical Slack badge

    Fixed Bugs

    • PR #311: Visual fix of padding in small inputField
    • PR #302: Rework of StylingClass & Remove trailing space from CSS class names
    • PR #299: Make annotation processor for lenses aware of constructor properties only
    • PR #300: Correct maven central for local development
    • PR #297: Fix styling bug in file component
    Source code(tar.gz)
    Source code(zip)
  • v0.9(Mar 2, 2021)

    Breaking Changes

    This release contains changes that break code written with earlier versions.

    Global Render Functions (PR#233 & PR#243)

    Up until fritz2 version 0.8, we offered two global render functions:

    • render {}: List<Tag<E>> - create RenderContext for multiple root elements
    • renderElement {}: Tag<E> - create RenderContext for single root element The result had to be mounted to your DOM-tree by calling, the mount() function.

    With version 0.9, we simplified this process by supplying one global render {} function which has no return value, but receives an HTMLElement / selector-String to specify your mount-target (which defaults to the body) as first parameter instead. The mount() function has been removed.

    The former global renderElement {} function has been removed as well, since it's executed exactly once and there were no performance gains in using it. Flow<T>.renderElement {} can and should of course still be used when you are sure that you render exactly one root element in it's RenderContext.

    See the changes to render functions in the following example code:

    // version 0.8
    render {
        h1 { +"My App" }
        div(id = "myDiv") {
            store.data.render { value ->
                if (value) div { +"on" } else span { +"off" }
            }
        }
    }.mount("target") // mount to ID "target"
    
    // version 0.9
    render("#target") { // mount to QuerySelector "#target"
        h1 { +"My App" }
        //div(id = "myDiv") {  // no need to wrap a Flow before rendering
            store.data.render { value ->
                if (value) div { +"on" } else span { +"off" }
            }
        //}
    }//.mount("target")
    

    Mounting fritz2-HTML directly to the document.body works without passing the first parameter to the render function because it's the default. With the additional override flag, you can specify whether or not you want to overwrite existing HTML content. This value defaults to true.

    Note: A QuerySelecor must be used instead of an id now, which is why you need to start your target string with "#".

    Changes in Repositories API (PR#232)

    We made some key changes to simplify the usage of fritz2 repositories (localstorage and REST). The Resource class and the ResourceSerializer interface where combined into the new interface Resource, which is now the only interface that needs to be implemented to create a repository. We also renamed the (de-)serialization functions:

    • write -> serialize
    • read -> deserialize
    • writeList -> serializeList - needed only for QueryRepository
    • readList -> deserializeList - needed only for QueryRepository

    The following example demonstrates the changes to fritz2 Repositories:

    @Lenses
    @Serializable
    data class ToDo(val id: Long = -1, val text: String = "", val completed: Boolean = false)
    
    // version 0.8
    object ToDoSerializer : Serializer<ToDo, String> {
        override fun read(msg: String): ToDo = Json.decodeFromString(ToDo.serializer(), msg)
        override fun readList(msg: String): List<ToDo> = Json.decodeFromString(ListSerializer(ToDo.serializer()), msg)
        override fun write(item: ToDo): String = Json.encodeToString(ToDo.serializer(), item)
        override fun writeList(items: List<ToDo>): String = Json.encodeToString(ListSerializer(ToDo.serializer()), items)
    }
    val toDoResource = Resource(ToDo::id, ToDoSerializer, ToDo())
    val query = restQuery<ToDo, Long, Unit>(toDoResource, "/api/todos")
    
    // version 0.9
    object ToDoResource : Resource<ToDo, Long> {
        override val idProvider: IdProvider<ToDo, Long> = ToDo::id
        override fun deserialize(msg: String): ToDo = Json.decodeFromString(ToDo.serializer(), msg)
        override fun deserializeList(msg: String): List<ToDo> = Json.decodeFromString(ListSerializer(ToDo.serializer()), msg)
        override fun serialize(item: ToDo): String = Json.encodeToString(ToDo.serializer(), item)
        override fun serializeList(items: List<ToDo>): String = Json.encodeToString(ListSerializer(ToDo.serializer()), items)
    }
    val query = restQuery<ToDo, Long, Unit>(ToDoResource, "/api/todos", initialId = -1)
    

    As you can see in this example, it is no longer necessary to supply Resource with a default instance. Instead, using a REST-Repository requires you to pass initialId to allow the repository to derive from a given instance whether a POST or PUT should be sent. This parameter is not needed for localstorage repositories.

    API Streamlining (PR#283)

    For the following fritz2 features we streamlined our API a bit:

    • Router
    • Tracking
    • History
    • Validator (note: renamed msgs to data)

    They now all have a data: Flow<X> and a current: X property which gives a dynamic Flow<X> or the static current value X of its state.

    Components (PR#269 & PR#266)

    In order to harmonize our component's API we have changed some fundamental aspects:

    • All component's properties follow a clear semantic: Just pass a value T or a Flow<T> directly as parameter and omit manually wrapping with flowOf:

      // version 0.8
      inputField {
          value { +"Hello world" }
      }
      inputField {
          value { flowOf("Hello world") }
      }
      
      // version 0.9
      inputField {
          value("Hello World")
      }
      
    • the base property for simple form wrapping components like inputField, pushButton and so on has been replaced by the element property.

    • instead of handling events directly within the component's top level configuration context, there is now an events context. So events must now be set up therein.

    • the items for all grouping forms have to be passed now directly as function parameter instead of setting it within the configuration context:

      // version 0.8
      checkboxGroup {
          items { items= listOf("A", "B", "C") }
      }
      
        // version 0.9
      checkboxGroup(items= listOf("A", "B", "C")) {
      }
      

      The following component's are affected by this:

      • checkboxGroup
      • radioGroup
      • selectField (new component)

    new features

    • added window event-listenersPR#277
    • added AppFrame and navigation-components PR#262
    • added file selector component PR#258
    • support for JS IR compiler PR#254
    • allow important to CSS-properties in DSL PR#248
    • components validation PR#244

    improvements

    • harmonized toast behavior PR#285
    • small refactoring for Router PR#281
    • added convenience function to register WebComponent PR#279
    • opened component's stores for extension PR#276
    • improved CloseButton API PR#275
    • small improvements to FormControl PR#272
    • added convenience functions for Lenses PR#271
    • rework element property prioritization PR#268
    • added convenience methods for Validator PR#242

    fixed bugs

    • fixed default styling of selectField PR#274
    • fixed placement of modals PR#249
    • adjusted WebComponents to new Render/TagContext PR#247
    Source code(tar.gz)
    Source code(zip)
  • v0.8(Dec 3, 2020)

    breaking changes

    This release contains changes that break code written with earlier versions. Hopefully these are the last major api-changes prior to fritz2 1.0:

    Setting attributes per function

    In fritz2 0.8 we decided to use functions to set attribute values instead of vars with delegation. That way you do not have to wrap constant values in a Flow anymore. This yields better performance and the const()-function could be removed. For convenience reasons we also added a new function asString for Flows to convert a Flow to a Flow<String> by calling the toString() method internally.

    input {
        type("text") // native
        value(myStore.data) // flow
        name(otherStore.data.asString()) // otherStore.data is not a Flow of String
    }
    

    RenderContext replaces HtmlElements

    We renamed the HtmlElements interface to RenderContext, because we think this name better fits the Kotlin DSL approach. The idea behind it is that every render function creates a new RenderContext in which new Tags can be created. This also means that you must replace the receiver type in your custom component-functions accordingly:

    val errorStore = storeOf("some text")
    
    // own component
    fun RenderContext.errorText(text: String): P {
        return p("error") {
            +text
        }
    }
    
    errorStore.data.render { //this: RenderContext
        errorText(it)
    }
    

    Adding Text and Comments

    We clarified the creation of TextNodes in Tags. Now you use unary +-operator for constant Strings to append text at this position to your Tag. If you have a Flow, call asText() instead. To create a CommentNode, you can use the !-operator and asComment() analogous. This intentionally follows a different approach in contrast to the attribute functions so it can be distinguished more easily.

    p {
        +"Hello "
        myStore.data.asText()
    
        !"this is a comment"
        myStore.data.asComment()
    }
    

    Evolution of render() and renderEach()

    Using former fritz2-versions you mapped a Flow of data to a Flow of Tags and created a MountPoint explicitly by calling bind() at some place in your rendering. This was error prone. Since nobody would do anything with a Flow<Tag> other than binding it, all render functions now implicitly create the mount point and therefore no bind() is necessary anymore. It has been removed completely.

    val myStore = storeOf(listOf("a","b","c"))
    
    render {
        ul {
        	myStore.data.renderEach {
        		li { +it }
        	} // no .bind() here anymore
        }
    }
    

    For performance reasons the render-functions prior to version 0.8 did not allow more than one root-element. In version 0.8 the standard render allows you to add as many root elements to your context as you want or even none:

    val myStore = storeOf(42)
    
    // renders multiple root-elements
    myStore.data.render {
    	repeat(it) {
    		div { +"one more" }
    	}
    }
    
    // does only render something if value is large enough
    myStore.data.render {
    	if (it > 100) {
    		div { +"number" }
    	}
    }
    

    If you you do not need this feature (because you know you will always have exactly one root-element) use renderElement() instead to get (slightly) improved performance.

    render() and renderElement() now reserve their place in the DOM until the content is rendered by using a temporary placeholder. Since this costs some performance you can disable it when you are sure that there are no sibling-elements on the same level in your DOM-tree by setting renderElement(preserveOrder = false). Use this when you have to render lots of elements (in huge lists, tables, etc.).

    Instead of someListFlow.each().render {...}.bind() you now simply write someListFlow.renderEach {...}. This is analog for all flavors of renderEach on Stores and Flows with and without an idProvider. Please note that renderEach() still allows only one root-element (like renderElement)!

    Tracker offers Flow<Boolean>

    Tracker now implements Flow<Boolean> instead of Flow<String?>so it adopts better to most use-cases. Find an example here.

    new features

    improvements

    • update all dependencies to latest version PR#166
    • extend Router functionality PR#197
    • upgraded Dokka-version and moved to html for api-docs PR#194
    • annotation processor visibility option PR#178
    • use local test server PR#165

    fixed bugs

    • fix memory leaks and performance issues PR#180 PR#185
    • no trailing slash in remote PR#167
    • fix boolean attribute delegates PR#172
    Source code(tar.gz)
    Source code(zip)
  • v0.7.2(Oct 15, 2020)

  • 0.7.1(Sep 6, 2020)

  • v0.7(Aug 13, 2020)

    breaking changes

    This release contains changes that break code written with earlier versions:

    • Handlers are now suspendable, so you can call suspend-methods directly inside your Handler. There is no need for Applicator anymore. Therefore this class and its utility-functions have been removed. (PR#124 & PR#126)
    • FormateStore and interface Format have been removed. Use format-factory-function inside lenses package to create a formatting Lens and create a normal SubStore (by using sub). (PR#139 & PR#146)
    val df: DateFormat = DateFormat("yyyy-MM-dd")
    // converts a Date into String in vice versa
    val dateFormat = format(
        parse = { df.parseDate(it) },
        format = { df.format(it) }
    )
    
    //using the dateLens
    val birthday = personStore.sub(L.Person.birthday + dateFormat)
    // or
    val birthday = personStore.sub(L.Person.birthday).sub(dateFormat)
    
    • Validation has been extracted as a service and refactored to be more concise. (PR#149 & #157)

    in commonMain

    data class Message(val id: String, val status: Status, val text: String) : ValidationMessage {
        override fun isError(): Boolean = status > Status.Valid // renamed from failed() -> isError()
    }
    
    object PersonValidator : Validator<Person, Message, String>() {
       // return your validation messages here
       override fun validate(data: Person, metadata: String): List<Message> {
           ...
       }
    }
    

    in jsMain

    val personStore = object : RootStore<Person>(Person()) {    
        // only update when it's valid
        val addOrUpdate = handle<Person> { oldPerson, newPerson ->
            if (PersonValidator.isValid(newPerson, "update")) new else oldPerson
        }
    }
    ...
    
    // then render the validation message list in your html
    PersonValidator.msgs.render { msg ->
        ...
    }.bind()
    

    in jvmMain

    if (PersonValidator.isValid(newPerson , "add")) {
        //e.g. save your new Person to Database
        ...
    } else {
       // get the messages, only available after isValid() was called
       val msgs = PersonValidator.msgs
       ...
    }
    

    new features

    • added tracking-service to access process state of Handlers (e.g. to show process indicator). (PR#147)
    • added history-service to keep track of historical values in Stores and provide back() function. (PR#152)
    • added Repository to offer CRUD-functionality for entities and dealing with queries. Implementations are available for REST and LocalStorage (see example). (PR#141, PR#144, PR#155 & PR#153)
    • added storeOf() function to create a minimal RootStore (without Handlers) (PR#144)
    • added convenience-function render on Seq, so you can directly write each(...).render { ... } (and leave out map) (PR#142)
    • added convenience-function render on Flow, so you can directly write flow.render { ... } (and leave out map) (PR#154)
    • added functions to deal with errors in Handlers (PR#137)
    • snapshots are now provided on oss.jfrog.org (PR#128)
    • added append function to remote (PR#127)
    • changed IdProvider to generic type (PR#123)
    • use Inspector (created by inspect()-function) to navigate through your model in validation and test and have data and corresponding ids available at any point (PR#118)

    fixed bugs

    • added isValid on JVM (PR#135)
    • added missing factories for <dt> and <dd> (PR#134)
    • added missing SelectedAttributeDelegate (PR#131)
    • fixed some bugs in Router and minor API changes (PR#151)
    Source code(tar.gz)
    Source code(zip)
  • v0.6(Jul 13, 2020)

    breaking changes

    This release contains changes that break code written with earlier versions:

    • You no longer need to inherit WithId in your model-classes (the interface has been removed from fritz2 entirely). Instead, you need to provide a function which returns the id of a certain instance. This function can be used when calling each or creating a SubStore for a certain element (PR#94):
    // in commonMain
    @Lenses
    data class Model(val id: String, val value: String)
    
    // in jsMain
    val store = RootStore<List<Model>>(listOf(...))
    
    render {
      ul {
        store.each(Model::id).map { modelStore ->
          render {
            li { modelStore.sub(L.Model.value).data.bind() }
          }
        }.bind()
      }
    }.mount("target")
    
    • All of the each methods (PR#113) were unified:

      • use Flow<T>.each() to map each instance of T to your Tags. It uses Kotlin's equality function to determine whether or not two elements are the same, and therefore re-renders the whole content you mapped when an element is changed or moved.

      • with Flow<T>.each(idProvider: (T) -> String) you can also map each instance of T to your Tags, but it uses the given idProvider to determine whether or not two elements are the same. In your mapping, you can get a SubStore for an element using listStore.sub(id, idProvider), so only the parts that actually changed will be re-rendered.

      • use Store<List<T>>.each() to map a SubStore<T> to Tags. It uses the list position of the element to determine whether or not two elements are the same. This means that when inserting something into the middle of the list, the changed element AND ALL following elements will be re-rendered.

      • with Store<List<T>>.each(idProvider: (T) -> String) you can also map a SubStore<T> to Tags, but it uses the given idProvider to determine whether or not two elements are the same`, so only the parts that actually changed will be re-rendered.

      • renamed handleAndEmit to handleAndOffer (PR#109)

      • renamed ModelIdRoot to RootModelId to follow the naming of stores (PR#96)

    new features

    • add static text in HTML by +"yourText" (PR#95)
    • add HTML-comments by comment("yourText") or !"yourText" (PR#108)
    • use the action function to dispatch an action at any point in your code (PR#117)

    fixed bugs

    • fixed handling of value and checked attributes (PR#81)
    • fixed MapRouter to use Map<String,String> (PR#82)
    • fixed double kotlin-block in gradle build-file (PR#97)
    • ensure order of children when mixing static tags with bound values on the same level by using bind(preserveOrder = true) (PR#102)
    • classes of HTML-tags are now open so you can inherit your own tags from them (PR#104)
    • SingleMountPoint for Boolean (leaving out the attribute if false) (PR#105)
    Source code(tar.gz)
    Source code(zip)
  • v0.5(Jun 11, 2020)

    breaking changes

    This release contains changes, that break code written with earlier versions:

    • We moved all artifacts and packages to match our domain: dev.fritz2. You will have to adjust your inputs and dependencies accordingly.
    • The default project-type for fritz2 now is multiplatform (to make it easy to share models and validation between client and server). Use the new fritz2-gradle-plugin to setup your project:

    build.gradle.kts

    plugins {
        id("dev.fritz2.fritz2-gradle") version "0.5"
    }
    
    repositories {
        jcenter()
    }
    
    kotlin {
        kotlin {
            jvm()
            js().browser()
    
            sourceSets {
                val commonMain by getting {
                    dependencies {
                        implementation(kotlin("stdlib"))
                    }
                }
                val jvmMain by getting {
                    dependencies {
                    }
                }
                val jsMain by getting {
                    dependencies {
                    }
                }
            }
        }
    }
    

    fixed bugs

    • fixed dom-update problem with checked attribute at HTMLInputElement
    Source code(tar.gz)
    Source code(zip)
  • v0.4(May 26, 2020)

    breaking changes

    This release contains changes, that break code written with earlier versions:

    • since it was the source of much confusion we renamed the function to build a tree of Tags (formerly html) to render:
    render {
        div("my-class") {
            // ...
        }
    }
    
    • the overloaded operator <= to bind a Flow of actions or events to a Handler was definitely not Kotlin-like, so we replaced it by the handledBy infix-function (please note the reversed order):
    button("btn btn-primary") {
        text("Add a dot")
        clicks handledBy store.addADot
    }
    

    new features

    • improved remote-api
    • support for building and using WebComponents

    bug fixes

    • improved examples
    • improved documentation

    build.gradle.kts

    Kotlin style

    dependencies {
        implementation("io.fritz2:fritz2-core-js:0.4")
    }
    

    Groovy style

    dependencies {
        implementation 'io.fritz2:fritz2-core-js:0.4'
    }
    
    Source code(tar.gz)
    Source code(zip)
  • v0.3(Apr 22, 2020)

    • several bug-fixes
    • tidyed up syntax for text, attr, const
    • better examples
    • Improved diff-algorithm for list-handling
    • better extractions on events (current value, selected item, etc.)
    • reworked structure of GitHub-projects
    Source code(tar.gz)
    Source code(zip)
  • v0.2(Mar 19, 2020)

  • v0.1(Feb 28, 2020)

Owner
Jens Stegemann
developing software since 1986... ;-)
Jens Stegemann
Web Container: A simple web container library for Android to help fellow developer to open WebView easily

WebContainer Description Web Container is a simple web container library for And

Achmad Ichsan Thaib 8 Nov 22, 2022
Clean MVVM with eliminating the usage of context from view models by introducing hilt for DI and sealed classes for displaying Errors in views using shared flows (one time event), and Stateflow for data

Clean ViewModel with Sealed Classes Following are the purposes of this repo Showing how you can remove the need of context in ViewModels. I. By using

Kashif Mehmood 22 Oct 26, 2022
Flowbius provides interoperability extensions for using Kotlin Flows with Mobius

Flowbius Flowbius provides interoperability extensions for using Kotlin Flows with Mobius. They allow conversion from Flows to Mobius types and vice v

Atlassian Labs 54 Jul 19, 2022
Saga pattern implementation in Kotlin build in top of Kotlin's Coroutines.

Module Saga Website can be found here Add in build.gradle.kts repositories { mavenCentral() } dependencies { implementation("io.github.nomisr

Simon Vergauwen 50 Dec 30, 2022
KVision allows you to build modern web applications with the Kotlin language

KVision allows you to build modern web applications with the Kotlin language, without any use of HTML, CSS or JavaScript. It gives you a rich hierarchy of ready to use GUI components, which can be used as builder blocks for the application UI.

Robert Jaros 985 Jan 1, 2023
Reactive setup with Spring WebFlux , Kotlin, Postgres and Spring Data R2DBC

Reactive Spring with Kotlin and Pg Spring WebFlux with Netty instead of Spring Web with Tomcat Mono and Flux in all layers (controller, service, repo)

Bimal Raj Gyawali 7 Dec 9, 2022
A project to learn about Reactive Microservices experimenting with architectures and patterns

reactive-microservices-workshop Copyright © 2021 Aleix Morgadas - Licenced under CC BY-SA 4.0 A project to learn about Reactive Microservices experime

Aleix Morgadas 7 Feb 21, 2022
Event-driven application uses React, reactive Spring Boot WebFlux, R2DBC, MySQL and Liquibase

Product delivery Event-driven application uses React, reactive Spring Boot WebFlux, R2DBC, MySQL and Liquibase Status: IN PROGRESS if [[ "" != `docker

Maksim Kostromin 2 Aug 17, 2022
In this Repo i create public apis to serve apps, like muslim apps using Spring, kotlin, and microservices

spring-freelance-apis-kotlin In this Repo i create public apis to serve apps, like muslim apps using Spring, kotlin, and microservices This repo for l

null 6 Feb 13, 2022
Showcase project of Functional Reactive Programming on Android, using RxJava.

FunctionalAndroidReference FunctionalAndroidReference is a showcase project of Functional Reactive Programming on Android, using RxJava. It's a compan

Paco 278 Nov 18, 2022
My personal template for a Spring Boot REST reactive webapp

My personal spring boot kotlin reactive template Features Spring Security implementation with JWT access and refresh token MongoDB database Project Co

Dušan 2 Dec 1, 2021
A podcast proxy that sits between itunes search api and android apps allowing normalization of rss feeds to standard Json format that can be consumed by apps.

Podcasts Rss Feeds Search Proxy A podcast proxy written using kotlin dsl that sits between itunes search api, podcasts rss feeds and android apps allo

8BitsLives .❤️ 2 Nov 27, 2022
Reach plc. Apps Team Exercise (Junior)Reach plc. Apps Team Exercise (Junior)

Reach plc. Apps Team Exercise (Junior) Description One of our magazines is looking for new sources of revenues and starts a few partnerships with beau

null 0 Nov 9, 2021
A multifunctional Android RAT with GUI based Web Panel without port forwarding.

AIRAVAT A multifunctional Android RAT with GUI based Web Panel without port forwarding. Features Read all the files of Internal Storage Download Any M

The One And Only 336 Dec 27, 2022
Candroid Browser is a replacement web browser for Candroid. It is designed to replace the AOSP browser, but not Google Chrome. It will be based on a privacy friendly WebKit engine fork.

Candroid Browser Candroid Browser is a replacement web browser for Candroid. It is designed to replace the AOSP browser, but not Google Chrome. It wil

Sean P. Myrick V19.1.7.2 3 Dec 22, 2022
🗡️ Deddit demonstrates modern Android development with Hilt, Coroutines, Flow, Jetpack, and Material Design based on MVVM architecture

Deddit demonstrates modern Android development with Hilt, Coroutines, Flow, Jetpack (ViewModel,Paging3), and Material Design based on MVVM

Krish Parekh 9 Sep 2, 2022
🛒 Mercado Libre App Clone using modern Android development with Hilt, Coroutines, Jetpack (Room, ViewModel), and Jetpack Compose based on MVVM architecture.

Meli Clone ?? Mercado Libre App Clone using modern Android development with Hilt, Coroutines, Jetpack (Room, ViewModel), and Jetpack Compose based on

Esteban Aragon 7 Sep 22, 2022
❤️ A sample Marvel heroes application based on MVVM (ViewModel, Coroutines, LiveData, Room, Repository, Koin) architecture.

MarvelHeroes MarvelHeroes is a demo application based on modern Android application tech-stacks and MVVM architecture. Fetching data from the network

Jaewoong Eum 1.2k Dec 19, 2022
🦁 A Disney app using transformation motions based on MVVM (ViewModel, Coroutines, Flow, LiveData, Room, Repository, Koin) architecture.

DisneyMotions A demo Disney app using transformation motions based on MVVM architecture. The motion system is included in the 1.2.0-alpha05 released m

Jaewoong Eum 1.4k Jan 2, 2023