Bot releases are hidden (Show)
Published by WestHuus over 3 years ago
The following things has been changed:
KeyboardEvent.key
instead of deprecated KeyboardEvent.keyCode
for comparisonKeys.Alt
, Keys.Enter
, Keys.Esc
)Key
by calling the constructor with the given KeyboardEvent
equals()
method which compares two Key
s by Key.key
attribute.toString()
returns the key
attributeinput {
keydowns.key().map {
when (it) {
Keys.Enter -> //...
Keys.Escape -> //...
else -> //..
}
} handledBy store.myHandler
}
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.
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.info
, success
, warning
, danger
and neutral
changed to ColorScheme
color
of our button
component refactors to type
and based on the ColorScheme
approach now.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") }
}
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.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")
}
}
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.
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
}
AlertVariantStyles
interfaceColors
interfaceMove 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 API for using the fritz2 styling DSL on standard HTML Tag
s.
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!"
}
}
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:
For a detailed overview have a look at our KitchenSink project.
both
for textArea componentFor 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
EventContexts
Published by haukesomm over 3 years ago
Published by jamowei over 3 years ago
This release contains changes that break code written with earlier versions.
Up until fritz2 version 0.8, we offered two global render functions:
render {}: List<Tag<E>>
- create RenderContext
for multiple root elementsrenderElement {}: Tag<E>
- create RenderContext
for single root elementmount()
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 "#".
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.
For the following fritz2 features we streamlined our API a bit:
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.
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)AppFrame
and navigation-components PR#262
FormControl
PR#272
Validator
PR#242
Published by jwstegemann almost 4 years ago
This release contains changes that break code written with earlier versions. Hopefully these are the last major api-changes prior to fritz2 1.0:
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 Flow
s 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 Tag
s 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)
}
We clarified the creation of TextNodes in Tag
s. Now you use unary +
-operator for constant String
s
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()
}
render()
and renderEach()
Using former fritz2-versions you mapped a Flow
of data to a Flow
of Tag
s 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 Store
s and Flow
s with and without an idProvider
.
Please note that renderEach()
still allows only one root-element (like renderElement
)!
Flow<Boolean>
Tracker
now implements Flow<Boolean>
instead of Flow<String?>
so it adopts better to most use-cases. Find an example here.
Published by jwstegemann about 4 years ago
Small patch resolving a memory issue related to coroutine scopes.
Published by jwstegemann about 4 years ago
Just a small patch to be compatible with Kotlin 1.4.0.
No new features or bug fixes included.
Published by jwstegemann about 4 years ago
This release contains changes that break code written with earlier versions:
Handler
s 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)
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
...
}
Handler
s (e.g. to show process indicator). (PR#147)Store
s and provide back()
function. (PR#152)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)storeOf()
function to create a minimal RootStore
(without Handler
s) (PR#144)render
on Seq
, so you can directly write each(...).render { ... }
(and leave out map
) (PR#142)render
on Flow
, so you can directly write flow.render { ... }
(and leave out map
) (PR#154)Handler
s (PR#137)append
function to remote (PR#127)IdProvider
to generic type (PR#123)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)Published by jwstegemann over 4 years ago
This release contains changes that break code written with earlier versions:
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 Tag
s. 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 Tag
s, 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 Tag
s. 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 Tag
s, 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)
+"yourText"
(PR#95)comment("yourText")
or !"yourText"
(PR#108)action
function to dispatch an action at any point in your code (PR#117)value
and checked
attributes (PR#81)MapRouter
to use Map<String,String>
(PR#82)kotlin
-block in gradle build-file (PR#97)bind(preserveOrder = true)
(PR#102)SingleMountPoint
for Boolean
(leaving out the attribute if false) (PR#105)Published by jwstegemann over 4 years ago
This release contains changes, that break code written with earlier versions:
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 {
}
}
}
}
}
checked
attribute at HTMLInputElement
Published by jwstegemann over 4 years ago
This release contains changes, that break code written with earlier versions:
Tag
s (formerly html
) to render
:render {
div("my-class") {
// ...
}
}
<=
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
}
Kotlin style
dependencies {
implementation("io.fritz2:fritz2-core-js:0.4")
}
Groovy style
dependencies {
implementation 'io.fritz2:fritz2-core-js:0.4'
}
Published by jwstegemann over 4 years ago
Published by jwstegemann over 4 years ago
First automated release using github actions...
Published by jwstegemann over 4 years ago
Our first public release, just to test the build- and publish-process.