Host apps in HTML elements
You can also provide an HTML element when launching a top-level Web app. This allows you to host Doodle apps in non-Doodle contexts. The apps in this documentation are top-level within specific elements.
Closing the page cleans up any apps within it. Removing the element hosting an app or explicitly calling shutdown
has the same effect.
package htmlelementapp
import UsefulApp
import io.nacular.doodle.application.application
import org.w3c.dom.HTMLElement
//sampleStart
fun main(element: HTMLElement) {
// launch app within element
application(element) {
UsefulApp()
}
}
//sampleEnd
Nested apps
Doodle Web apps can be run within other Doodle Web apps. This is done by placing the nested app in a View that the host app manages. An app launched this way has the same functionality as a top-level one. Its lifecycle however, is tied to the host View.
You simply use an ApplicationViewFactory
(available via the AppViewModule
) to create nested apps.
- Inner App
- Outer App
- Outer App Launcher
package io.nacular.doodle.docs.apps.nesting
import io.nacular.doodle.application.Application
import io.nacular.doodle.controls.ColorPicker
import io.nacular.doodle.core.Display
import io.nacular.doodle.docs.utils.doodleColor
import io.nacular.doodle.drawing.opacity
import io.nacular.doodle.layout.Insets
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.layout.constraints.fill
//sampleStart
class InnerApp(display: Display): Application {
init {
// Shows a color picker
display += ColorPicker(doodleColor opacity 0.75f).apply {
changed += { _,_,new -> println(new) }
}
// The picker grows with the display, but is inset a little
display.layout = constrain(display.first(), fill(insets = Insets(2.0)))
}
override fun shutdown() {}
}
//sampleEnd
package io.nacular.doodle.docs.apps.nesting
import io.nacular.doodle.application.Application
import io.nacular.doodle.application.ApplicationViewFactory
import io.nacular.doodle.application.Modules.Companion.PointerModule
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.center
import io.nacular.doodle.core.height
import io.nacular.doodle.core.width
import io.nacular.doodle.drawing.Color.Companion.White
import io.nacular.doodle.drawing.paint
import io.nacular.doodle.geometry.Rectangle
import io.nacular.doodle.geometry.centered
import io.nacular.doodle.utils.Resizer
import org.kodein.di.instance
import kotlin.math.min
//sampleStart
class OuterApp(display: Display, appView: ApplicationViewFactory): Application {
init {
// NOTE: Any module needed by InnerApp MUST be provided to the factory.
// PointerModule used here to enable interaction with the color picker.
display += appView(modules = listOf(PointerModule)) {
InnerApp(display = instance()) // Init inner app
}.apply {
bounds = Rectangle(
min(400.0, display.width - 20),
min(300.0, display.height - 20)
).centered(at = display.center)
Resizer(this).apply { movable = false }
}
display.fill(White.paint)
}
override fun shutdown() {}
}
//sampleEnd
package outerapp
import io.nacular.doodle.application.ApplicationViewFactory.Companion.AppViewModule
import io.nacular.doodle.application.Modules.Companion.PointerModule
import io.nacular.doodle.application.application
import io.nacular.doodle.docs.apps.nesting.OuterApp
import org.kodein.di.instance
fun main() {
//sampleStart
// NOTE: Modules used by the outer app are not available to the inner one.
// The PointerModule is used here to allow resizing of the View that holds the inner app.
application(modules = listOf(AppViewModule, PointerModule)) {
OuterApp(display = instance(), appView = instance())
}
//sampleEnd
}
Adding a nested app's View to the Display triggers the app's initialization. Shutdown the app by removing the host View from the Display.
Host arbitrary HTML elements
You can embed any HTML element into your app as a View
. This means Doodle apps can host React and other web components and interop with a much larger part of the Web ecosystem out of the box!
- App
- Example Launcher
package io.nacular.doodle.docs.apps
import io.nacular.doodle.HtmlElementViewFactory
import io.nacular.doodle.animation.Animator
import io.nacular.doodle.application.Application
import io.nacular.doodle.controls.text.Label
import io.nacular.doodle.core.Display
import io.nacular.doodle.core.then
import io.nacular.doodle.docs.utils.DateRangeSelectionModel
import io.nacular.doodle.docs.utils.HorizontalCalendar
import io.nacular.doodle.docs.utils.ShadowCard
import io.nacular.doodle.drawing.Font
import io.nacular.doodle.geometry.PathMetrics
import io.nacular.doodle.layout.constraints.constrain
import io.nacular.doodle.theme.Theme
import io.nacular.doodle.theme.ThemeManager
import kotlinx.datetime.DatePeriod
import kotlinx.datetime.LocalDate
import kotlinx.datetime.plus
import org.w3c.dom.HTMLElement
class ReactCalendarApp(
display : Display,
font : Font,
today : LocalDate,
animate : Animator,
pathMetrics : PathMetrics,
themeManager : ThemeManager,
theme : Theme,
htmlElementView: HtmlElementViewFactory,
reactCalendar : HTMLElement,
appHeight : (Double) -> Unit
): Application {
private val doodleCalendar = HorizontalCalendar(
today,
animate,
pathMetrics,
startDate = today,
endDate = today + DatePeriod(years = 10),
DateRangeSelectionModel()
).apply {
this.font = font
}
init {
themeManager.selected = theme
//sampleStart
display += Label("Doodle").apply { this.font = font }
display += ShadowCard(doodleCalendar)
display += Label("React").apply { this.font = font }
display += htmlElementView(element = reactCalendar)
//sampleEnd
val spacing = 20
display.layout = constrain(
display.children[0],
display.children[1],
display.children[2],
display.children[3]
) { doodleLabel, doodle, reactLabel, react ->
doodle.top eq doodleLabel.bottom + spacing
doodle.height eq 280
react.height eq doodle.height
doodleLabel.top eq spacing
doodleLabel.centerX eq doodle.centerX
doodleLabel.width.preserve
doodleLabel.height.preserve
reactLabel.centerX eq react.centerX
reactLabel.width.preserve
reactLabel.height.preserve
when {
parent.width.readOnly > 800 -> {
doodle.width eq (parent.width - 3 * spacing) / 2
doodle.right eq parent.centerX - spacing / 2
react.top eq doodle.top
react.width eq doodle.width
react.left eq doodle.right + spacing
reactLabel.top eq doodleLabel.top
}
else -> {
doodle.left eq spacing
doodle.right eq parent.right - spacing
react.top eq reactLabel.bottom + spacing
react.left eq spacing
react.right eq parent.right - spacing
reactLabel.top eq doodle.bottom + spacing
}
}
}.then { container ->
// signal to outer docs about height of the app
appHeight(container.children.maxOf { it.bounds.bottom } + spacing)
}
}
override fun shutdown() {
// no-op
}
}
package elementview
import io.nacular.doodle.HtmlElementViewFactory
import io.nacular.doodle.application.Application
import io.nacular.doodle.application.HtmlElementViewModule
import io.nacular.doodle.application.Modules
import io.nacular.doodle.application.application
import io.nacular.doodle.core.Display
import org.kodein.di.instance
import org.w3c.dom.HTMLElement
private class MyApp(
display : Display,
viewFactory: HtmlElementViewFactory,
element : HTMLElement
): Application {
init {
display += viewFactory(element)
}
override fun shutdown() {}
}
fun main(element: HTMLElement) {
//sampleStart
application(modules = listOf(Modules.HtmlElementViewModule)) {
MyApp(display = instance(), viewFactory = instance(), element = element)
}
//sampleEnd
}
This app embeds a react-calendar.