Skip to main content

Web

Coming from Desktop, let's close things out with what's unique to running in the browser — and it's a satisfying place to land, since two things get promised earlier in this guide and finally paid off here.

Host apps in HTML elements​

Back on Displays, this was promised but not shown: launching a top-level Web app inside a specific HTML element, rather than letting it take over the whole page. Here it is — pass the element to application, and everything else about your app is completely unaffected. (Every example throughout this documentation is, in fact, hosted exactly this way.)

package htmlelementapp import usefulapp.HelloApp import io.nacular.doodle.application.application import org.w3c.dom.HTMLElement //sampleStart fun main(element: HTMLElement) { // launch app within element application(element) { HelloApp() } } //sampleEnd

Cleanup happens automatically, in more ways than one: closing the page tears down every app within it, and so does removing the element hosting a given app — or you can call shutdown on it explicitly, same as always.

Nested apps​

The other promise, from all the way back on Applications: a Doodle Web app can run inside another Doodle Web app. Place the nested app in a View that the host app manages, and it behaves exactly like a top-level app in every way that matters — the one difference is its lifecycle, which is now tied to that host View instead of the page.

Build one with an ApplicationViewFactory, available through the AppViewModule:

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
tip

Adding a nested app's View to the Display is what triggers that app's initialization. Removing the host View from the Display shuts it down.

Host arbitrary HTML elements​

This is the one that really opens things up: any HTML element can be embedded into your app as a View. That means a Doodle app can host React components — or anything else from the Web ecosystem — right alongside its own Views, not walled off from them.

The example below embeds a real react-calendar component next to a Doodle-native calendar built with the same constraints you've been using throughout this guide:

Mon
Tue
Wed
Thu
Fri
Sat
Sun
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.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.Strength.Companion.Required import io.nacular.doodle.layout.constraints.constrain import io.nacular.doodle.layout.constraints.ideal import io.nacular.doodle.layout.constraints.minus 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 = today, animate = animate, pathMetrics = pathMetrics, startDate = today, endDate = today + DatePeriod(years = 10), selectionModel = DateRangeSelectionModel() ).apply { this.font = font } init { themeManager.selected = theme //sampleStart display += Label("Doodle").apply { this.font = font } display += Label("React" ).apply { this.font = font } display += ShadowCard(doodleCalendar) display += htmlElementView(element = reactCalendar) //sampleEnd val spacing = 20 display.children.last().boundsChanged += { _,_,new -> // signal to outer docs about height of the app appHeight(display.children.maxOf { it.bounds.bottom } + spacing) } display.layout = constrain( display.children[0], display.children[1], display.children[2], display.children[3] ) { doodleLabel, reactLabel, doodle, 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.size prefers ideal reactLabel.centerX eq react.centerX reactLabel.size prefers ideal when { parent.width.value > 800 -> { doodle.width eq (parent.width - 3 * spacing) / 2 strength Required - 1 doodle.right eq parent.centerX - spacing / 2 strength Required - 1 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 strength Required - 1 react.top eq reactLabel.bottom + spacing react.left eq spacing react.right eq doodle.right reactLabel.top eq doodle.bottom + spacing } } } } override fun shutdown() { // no-op } }

Recap​

  • Launch a Web app inside a specific element with application instead of taking over the whole page — this is how every live example in this documentation actually runs.
  • A nested app behaves just like a top-level one, except its lifecycle is tied to the View hosting it rather than the page.
  • Any HTML element — including a whole React component — can become a View and sit directly alongside your Doodle content.

That's the whole guide. From Hello Doodle through here, you've now seen every piece: building and launching an app, the Display and View hierarchy, drawing with Canvas, sizing and positioning Views, responding to input, transforms and animation, drag-and-drop, popups, accessibility, theming and the full component library, and what's unique to each platform.

From here, the best next steps are outside this guided tour: Tutorials has complete, real-world apps to read through or fork, and the API Docs are there for whenever you need the exact signature of something rather than an explanation of it. Good luck building.