Skip to content

squins/kwmdsl

Repository files navigation

Introduction

The Kotlin Wicket markup DSL enables you to write markup in the Kotlin source file, instead of in a separate HTML file. The markup uses references to component properties or functions, so the wicket ID only has to be defined once and will be updated in all places using a rename refactoring. The hierarchy also only has to be specified once: in the markup. Components are added by the markup to the page or component according to the markup hierarchy.

Properties

  • Easy to learn: it is just Wicket written differently.
  • You can use the minimal markup that Wicket understands just like with HTML markup.
  • The markup is static jsut like HTML markup. Dynamic interfaces must use the visibility of components, panel replacement, etc.
  • The actual markup string is generated once per component class.
  • Only plain text is escaped. HTML element and attribute names and attribute values are output as is.
  • Makes navigation between code and markup simple. Jump to source and go to declaration or usages in IntelliJ.
  • Adoption can be done incrementally. It is not necessary to convert large parts of your application to be able to use the DSL. Each component can be converted independently.
  • It is a small library. Most of the code contains (generated) convenience functions for HTML elements and attributes.

Status

The DSL is currently being used for pages with dozens of components, spread across multiple panels. What is currently lacking are a lot of tests, and comprehensive documentation. The examples do demonstrate that it is working correctly for lots of cases.

There is still work to be done for fragments. Not all possibilities that could be supported are supported.

How to Use

Dependency

The library is available on Maven Central. Use the following coordinates. For Maven:

<dependency>
    <groupId>com.squins</groupId>
    <artifactId>kotlin-wicket-markup-dsl</artifactId>
    <version>4</version>
</dependency>

For Gradle:

com.squins:kotlin-wicket-markup-dsl:4

For Gradle, in a TOML file:

kwmdsl = { group = "com.squins", name = "kotlin-wicket-markup-dsl", version = "4" }

Basic Usage

Here is the Hello World! example of Wicket in Kotlin using the DSL. There is a simple mapping from the standard Wicket code to the DSL, so it is easy to convert, and the resulting code is easibly recognizable as Wicket code.

See below for an explanation of the lines the numbers are on:

class HelloWorldPage : KotlinWicketMarkupWebPage<Unit>() { // 1
   private val message: Label = Label(::message.name, "Hello World!") // 2 

   init {
       noVariantMarkup.addTo(this) // 5
   }

   companion object : IKotlinWicketMarkupProvider {
       override val noVariantMarkup = markup { // 3
           html {
               body {
                   span(HelloWorldPage::message) { text("Message goes here") } // 4
               }
           }
       }
   }
}
  1. Use a base class that can find the markup generated by the DSL. The base classes are provided as a convenience. The DSL can be used with existing components that have a specialized base class. See below.
  2. Define the component as a property. The property name is the component ID. Everywhere the ID is needed (component constructor, markup, resource reference, etc.), a reference to the property will be used.
  3. Define the markup in the companion object, which must implement IKotlinWicketMarkupProvider. In this case only markup for the no-variants (no style, variation and/or locale) case is specified.
  4. In the markup refer to the componentns using a references to the component property.
  5. Tell the markup to add the hierarchy of descendent components to the page.

Core Constructs

Note that this is a very short overview of what constructs make up the DSL. Extensive documentation is provided elsewhere. See more information below.

Base Classes

The DSL can be integrated into exsiting pages and components.

If the page or component derives from a specialized base class, the page or component must implement IMarkupResourceStreamProvider and implement fun getMarkupResourceStream(container: MarkupContainer, containerClass: Class<*>) to call the DSL function findMarkup(container, containerClass).

If the page or component is new it can use, or if an existing page or component uses a standard base class it can be switched, to one of the convenience base classes that already do the above. These classes start with KotlinWicketMarkup with the standard base class name appended to it.

In the examples:

Markup Definition Functions

Markup has to be defined in the companion object of the page or component class. The companion object must implement IKotlinWicketMarkupProvider. The property noVariantMarkup must be implemented, and use the markup definition function matching the component type:

  • markup { ... } for most components.
  • borderMarkup { ... } for Borders.
  • fragmentBodyMarkup { ... } for Fragment markup that is embedded in another (usually an ancestor) component.
  • standaloneFragmentMarkup { ... } for Fragments that have their own markup.

If the markup does not contain descendent components, the class that contains the markup must be specified explicitly.

Variants based on style, variation and/or locale can also be specified. It is not possible to mix the DSL and HTML in the same class: all markup of a class must be written using the DSL or HTML.

If the development configuration is used, the DSL will check that the component hierarchy of a variant of regular components and borders matches the hierarchy of the no-variant markup. Checking for matching hierarchies of the bodies of embedded fragments is not done automatically. You have to pass another fragment markup to the function used to define the fragment body markup.

In the examples:

Markup Building Functions

To write the markup, functions for elements and attributes are provided. The functions for HTML elements and attributes are purely for convenience, and replacing them with their implementation will not break anything. But not all the functions for Wicket elements and attributes can replaced easily, as some Wicket constructs have specific behavior. The DSL knows about this behavior and ensures that the generated markup and component hierarchy will be correct for those Wicket constructs.

All examples will show use of the Wicket and/or HTML elements and attributes.

Wicket Elements and Attributes

Functions for Wicket elements start with wicket and functions for Wicket attributes start with attrWicket. Simply use these functions in the markup definiton where you would normally use a Wicket element or attribute.

To promote the use of <wicket:message> and the wicket:message attribute instead of static text, shorthand functions are available: m(...) and am(...) respectively.

HTML Elements and Attributes

There are functions for each (non-deprecated) HTML element and attribute.

For elements 4 functions with the following parameters are provided:

  • Only attributes.
  • A reference to a component property, and attributes.
  • A Repeated object referencing a component function, and attributes.
  • A Wicket ID and attributes. This function should be avoided if possible as renaming will not update these Wicket IDs, but it may be needed for subclasses that override the markup of their base class.

All HTML attribute functions start with attr. How many overloads there are depends on what values are allowed for the attribute. Some attributes are usable for multiple tags with different sets of allowed values. An overload will be provided for each set of values.

Where the chance exists that new attribute values may be added in the future, an overload accepting a String is usually provided. This way the new value can be used without requiring a new DSL release.

Page and Component References

When a reference to a page or a component is needed in the markup, references instead of literal paths can be used. This ensures that renaming and moving of components, and changes to the hierarchy are picked up automatically.

References can be used in the following situations:

  • The wicket:for attribute of <label>.
  • The for attribute of <wicket:label>.
  • To pages and scope components for resources inside <wicket:link>.

In the examples:

Hierarchy Addition

Now that the page and component indicate they provide their markup in a custom way, the companion object defines all markup variants, the only thing left to do is to add the hierarchy of the descendent components to the component. This can simply be done by invoking the approriate function on the markup:

  • addTo(...) for adding descendents to most components.
  • addToBorder(...) for adding descendents of Borders.
  • addToFragment(...) for adding descendents to Fragments, with markup provided by either the fragment itself or another component.

In the examples:

Terse Code

The markup code can be more terse by introducing a type alias, but due to a limitation (bug?) in the Kotlin toolchain, private type aliases within the same package have to have a unique name.

In the examples:

Example Code

Almost all features are demonstrated in the examples. See the example source files.

The examples can be run using KwmDslApp.kt. See the instructions written to the terminal.

More Information

There is a lot more information about the DSL. Please see:

About

A lightweight Kotlin DSL for specifying markup for Wicket components

Topics

Resources

License

Stars

1 star

Watchers

1 watching

Forks

Packages

 
 
 

Contributors