Skip to content

outwatch/outwatch.github.io

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><title>readme · Outwatch</title><meta name="viewport" content="width=device-width, initial-scale=1.0"/><meta name="generator" content="Docusaurus"/><meta name="description" content="Welcome to Outwatch!"/><meta name="docsearch:language" content="en"/><meta property="og:title" content="readme · Outwatch"/><meta property="og:type" content="website"/><meta property="og:url" content="https://outwatch.github.io/"/><meta property="og:description" content="Welcome to Outwatch!"/><meta name="twitter:card" content="summary"/><link rel="shortcut icon" href="/"/><link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css"/><script type="text/javascript" src="https://buttons.github.io/buttons.js"></script><script src="/js/scrollSpy.js"></script><link rel="stylesheet" href="/css/main.css"/><script src="/js/codetabs.js"></script></head><body class="sideNavVisible separateOnPageNav"><div class="fixedHeaderContainer"><div class="headerWrapper wrapper"><header><a href="/"><h2 class="headerTitle">Outwatch</h2></a><div class="navigationWrapper navigationSlider"><nav class="slidingNav"><ul class="nav-site nav-site-internal"></ul></nav></div></header></div></div><div class="navPusher singleRowMobileNav"><div class="docMainWrapper wrapper"><div class="container mainContainer docsContainer"><div class="wrapper"><div class="post"><header class="postHeader"><h1 id="__docusaurus" class="postHeaderTitle">readme</h1></header><article><div><span><p>Welcome to Outwatch!
We hope you enjoy this documentation. If you find something that can be improved, please do! Every Pull Request with a big or small improvement is very much appreciated. See <a href="#improving-the-documentation">Improving The Documentation</a>.</p>
<h2><a class="anchor" aria-hidden="true" id="getting-started"></a><a href="#getting-started" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Getting started</h2>
<h3><a class="anchor" aria-hidden="true" id="start-with-a-template"></a><a href="#start-with-a-template" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Start with a template</h3>
<p>For a quick start, install <code>java</code>, <code>sbt</code>, <code>nodejs</code>, <code>yarn</code> and <code>github-cli</code> (optionally) and use the following github template:</p>
<pre><code class="hljs css language-shell"><span class="hljs-meta">#</span><span class="bash"> create new repo on github based on this template</span>
gh repo create my-first-outwatch-project --template outwatch/example --public --clone
<span class="hljs-meta">
#</span><span class="bash"> <span class="hljs-keyword">if</span> you want to just get the template locally without creating a github repo:</span>
git clone --depth 1 https://github.com/outwatch/example my-first-outwatch-project

</code></pre>
<p>In your newly created project folder, run:</p>
<pre><code class="hljs css language-bash">sbt dev
</code></pre>
<p>Now point your browser to <a href="http://localhost:12345">http://localhost:12345</a>.</p>
<p>Changes to the code will trigger a recompile and automatically refresh the page in the browser.</p>
<h3><a class="anchor" aria-hidden="true" id="create-a-project-from-scratch"></a><a href="#create-a-project-from-scratch" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Create a project from scratch</h3>
<p>Make sure that <code>java</code>, <code>sbt</code>, <code>nodejs</code> and <code>yarn</code> are installed.
Create a new SBT project and add the <code>scalajs</code> and <code>scalajs-bundler</code> plugins to your <code>project/plugins.sbt</code>:</p>
<pre><code class="hljs css language-scala">addSbtPlugin(<span class="hljs-string">"org.scala-js"</span> % <span class="hljs-string">"sbt-scalajs"</span> % <span class="hljs-string">"1.x.x"</span>)
addSbtPlugin(<span class="hljs-string">"ch.epfl.scala"</span> % <span class="hljs-string">"sbt-scalajs-bundler"</span> % <span class="hljs-string">"x.x.x"</span>)
</code></pre>
<p>Add the outwatch dependencies to your <code>build.sbt</code>:</p>
<pre><code class="hljs css language-scala">libraryDependencies ++= <span class="hljs-type">Seq</span>(
  <span class="hljs-string">"io.github.outwatch"</span>   %%% <span class="hljs-string">"outwatch"</span>          % <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-comment">// optional dependencies:</span>
  <span class="hljs-string">"com.github.cornerman"</span> %%% <span class="hljs-string">"colibri-zio"</span>       % <span class="hljs-string">"0.5.0"</span>, <span class="hljs-comment">// zio support</span>
  <span class="hljs-string">"com.github.cornerman"</span> %%% <span class="hljs-string">"colibri-fs2"</span>       % <span class="hljs-string">"0.5.0"</span>, <span class="hljs-comment">// fs2 support</span>
  <span class="hljs-string">"com.github.cornerman"</span> %%% <span class="hljs-string">"colibri-airstream"</span> % <span class="hljs-string">"0.5.0"</span>, <span class="hljs-comment">// sirstream support</span>
  <span class="hljs-string">"com.github.cornerman"</span> %%% <span class="hljs-string">"colibri-rx"</span>        % <span class="hljs-string">"0.5.0"</span>, <span class="hljs-comment">// scala.rx support</span>
  <span class="hljs-string">"com.github.cornerman"</span> %%% <span class="hljs-string">"colibri-router"</span>    % <span class="hljs-string">"0.5.0"</span>, <span class="hljs-comment">// Url Router support</span>
)

</code></pre>
<p>Enable the <code>scalajs-bundler</code> plugin:</p>
<pre><code class="hljs css language-scala">enablePlugins(<span class="hljs-type">ScalaJSPlugin</span>)
enablePlugins(<span class="hljs-type">ScalaJSBundlerPlugin</span>)
</code></pre>
<p>To configure hot reloading with webpack devserver, check out <a href="https://github.com/Outwatch/seed.g8/blob/master/src/main/g8/build.sbt">build.sbt</a> and <a href="https://github.com/Outwatch/seed.g8/blob/master/src/main/g8/webpack.config.dev.js">webpack.config.dev.js</a> from the <a href="https://github.com/Outwatch/seed.g8">g8 template</a>.</p>
<p>If anything is not working, cross-check how things are done in the template.</p>
<p>We're using <a href="https://jitpack.io">JitPack</a> to release the libraries. With JitPack you can easily try the latest features from specific commits on <code>master</code>, other branches or PRs. Just point <code>outwatchVersion</code> to a specific commit.</p>
<h3><a class="anchor" aria-hidden="true" id="use-common-javascript-libraries-with-outwatch"></a><a href="#use-common-javascript-libraries-with-outwatch" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Use common javascript libraries with Outwatch</h3>
<p>We have prepared helpers for some javascript libraries. You can find them in the <a href="https://github.com/outwatch/outwatch-libs">Outwatch-libs</a> Repository.</p>
<h3><a class="anchor" aria-hidden="true" id="convert-html-to-outwatch"></a><a href="#convert-html-to-outwatch" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Convert html to outwatch</h3>
<p>You can convert html code to outwatch code on this wonderful page: <a href="https://simerplaha.github.io/html-to-scala-converter/">https://simerplaha.github.io/html-to-scala-converter/</a></p>
<h2><a class="anchor" aria-hidden="true" id="examples"></a><a href="#examples" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Examples</h2>
<h2><a class="anchor" aria-hidden="true" id="hello-world"></a><a href="#hello-world" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Hello World</h2>
<p>In your html file, create an element, which you want to replace with dynamic content:</p>
<pre><code class="hljs css language-html">...
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- your compiled javascript should be imported here --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
...
</code></pre>
<p>To render html content with outwatch, create a component and render it into the given element:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> outwatch._
<span class="hljs-keyword">import</span> outwatch.dsl._
<span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>


<span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">Main</span> </span>{
  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span></span>(args: <span class="hljs-type">Array</span>[<span class="hljs-type">String</span>]): <span class="hljs-type">Unit</span> = {

    <span class="hljs-keyword">val</span> myComponent = div(<span class="hljs-string">"Hello World"</span>)

    <span class="hljs-type">Outwatch</span>.renderReplace[<span class="hljs-type">SyncIO</span>](<span class="hljs-string">"#app"</span>, myComponent).unsafeRunSync()
  }
}
</code></pre>
<p>Running <code>Main</code> will replace <code>&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;</code> with <code>myComponent</code>:</p>
<pre><code class="hljs css language-html">...
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
...
</code></pre>
<h2><a class="anchor" aria-hidden="true" id="interactive-counter"></a><a href="#interactive-counter" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Interactive Counter</h2>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> outwatch._
<span class="hljs-keyword">import</span> outwatch.dsl._
<span class="hljs-keyword">import</span> colibri._
<span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> counter = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-number">0</span>)
  div(
    button(<span class="hljs-string">"+"</span>, onClick(counter.map(_ + <span class="hljs-number">1</span>)) --&gt; counter),
    counter,
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run2" data-mdoc-js></div>
<p>To understand how this example works in-depth, please read about <a href="#dynamic-content">Dynamic Content</a> and <a href="#handling-events">Handling Events</a>.</p>
<p><strong>Important:</strong> In your application, <code>Outwatch.renderReplace</code> should be called only once at the end of the main method. To create dynamic content, you will design your data-flow with <code>Obvervable</code>, <code>Subject</code> and/or <code>Scala.Rx</code> and then instantiate outwatch only once with this method call. Before that, no reactive subscriptions will happen.</p>
<h2><a class="anchor" aria-hidden="true" id="static-content"></a><a href="#static-content" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Static Content</h2>
<p>First, we will focus on creating immutable/static content that will not change over time. The following examples illustrate to construct and transform HTML/SVG tags, attributes and inline stylesheets.</p>
<p><strong>Note:</strong> We created the helpers <code>showHTMLForDoc</code> and <code>docPreview</code> for showing results in this documentation. They are not part of outwatch.</p>
<h3><a class="anchor" aria-hidden="true" id="imports"></a><a href="#imports" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Imports</h3>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> outwatch._
<span class="hljs-keyword">import</span> outwatch.dsl._
<span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="concatenating-strings"></a><a href="#concatenating-strings" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Concatenating Strings</h3>
<pre><code class="hljs css language-scala">div(<span class="hljs-string">"Hello "</span>, <span class="hljs-string">"World"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run5" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="nesting"></a><a href="#nesting" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Nesting</h3>
<pre><code class="hljs css language-scala">div(span(<span class="hljs-string">"Hey "</span>, b(<span class="hljs-string">"you"</span>), <span class="hljs-string">"!"</span>)).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run6" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="primitives"></a><a href="#primitives" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Primitives</h3>
<pre><code class="hljs css language-scala">div(<span class="hljs-literal">true</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1000</span>L, <span class="hljs-number">3.0</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run7" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="attributes"></a><a href="#attributes" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attributes</h3>
<pre><code class="hljs css language-scala">div(idAttr := <span class="hljs-string">"test"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run8" data-mdoc-js></div>
<p>The order of content and attributes does not matter.</p>
<pre><code class="hljs css language-scala">div(<span class="hljs-string">"How "</span>, idAttr := <span class="hljs-string">"test"</span>, <span class="hljs-string">"are"</span>, title := <span class="hljs-string">"cool"</span>, <span class="hljs-string">" you?"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run9" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="styles"></a><a href="#styles" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Styles</h3>
<p>All style properties have to be written in <em>camelCase</em>.</p>
<pre><code class="hljs css language-scala">div(backgroundColor := <span class="hljs-string">"tomato"</span>, <span class="hljs-string">"Hello"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run10" data-mdoc-js></div>
<p>Multiple styles will me merged to one style attribute:</p>
<pre><code class="hljs css language-scala">div(
  backgroundColor := <span class="hljs-string">"powderblue"</span>,
  border := <span class="hljs-string">"2px solid #222"</span>,
  <span class="hljs-string">"Hello"</span>,
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run11" data-mdoc-js></div>
<p>Again, the order of styles, attributes and inner tags does not matter:</p>
<pre><code class="hljs css language-scala">div(
  h1(<span class="hljs-string">"Welcome to my website"</span>),
  backgroundColor := <span class="hljs-string">"powderblue"</span>,
  idAttr := <span class="hljs-string">"header"</span>
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run12" data-mdoc-js></div>
<p>Some styles have type safe values:</p>
<pre><code class="hljs css language-scala">div(cursor.pointer, fontWeight.bold, display.flex).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run13" data-mdoc-js></div>
<p>If you are missing more type safe values, please contribute to <a href="https://github.com/raquo/scala-dom-types">Scala Dom Types</a>. Example implementation: <a href="https://github.com/raquo/scala-dom-types/blob/master/shared/src/main/scala/com/raquo/domtypes/generic/defs/styles/Styles.scala#L1711">fontWeight</a></p>
<h3><a class="anchor" aria-hidden="true" id="reserved-scala-keywords-class-for-type"></a><a href="#reserved-scala-keywords-class-for-type" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reserved Scala keywords: class, for, type</h3>
<p>There are some attributes and styles which are reserved scala keywords. You can use them with backticks:</p>
<pre><code class="hljs css language-scala">div(`<span class="hljs-class"><span class="hljs-keyword">class</span>` </span>:= <span class="hljs-string">"item"</span>, <span class="hljs-string">"My Item"</span>).showHTMLForDoc(docPreview)
label(`<span class="hljs-keyword">for</span>` := <span class="hljs-string">"inputid"</span>).showHTMLForDoc(docPreview)
input(`<span class="hljs-class"><span class="hljs-keyword">type</span>` </span>:= <span class="hljs-string">"text"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run14" data-mdoc-js></div>
<p>There are shortcuts for the class and type atrributes:</p>
<pre><code class="hljs css language-scala">div(cls := <span class="hljs-string">"myclass"</span>).showHTMLForDoc(docPreview)
input(tpe := <span class="hljs-string">"text"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run15" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="overriding-attributes"></a><a href="#overriding-attributes" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Overriding attributes</h3>
<p>Attributes and styles with the same name will be overwritten. Last wins.</p>
<pre><code class="hljs css language-scala">div(color := <span class="hljs-string">"blue"</span>, color := <span class="hljs-string">"green"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run16" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="css-class-accumulation"></a><a href="#css-class-accumulation" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>CSS class accumulation</h3>
<p>Classes are not overwritten, they accumulate.</p>
<pre><code class="hljs css language-scala">div(cls := <span class="hljs-string">"tiny"</span>, cls := <span class="hljs-string">"button"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run17" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="custom-attributes-styles-and-tags"></a><a href="#custom-attributes-styles-and-tags" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Custom attributes, styles and tags</h3>
<p>All the tags, attributes and styles available in outwatch come from <a href="https://github.com/raquo/scala-dom-types">Scala Dom Types</a>.
If you want to use something not available in Scala Dom Types, you can use custom builders:</p>
<pre><code class="hljs css language-scala"><span class="hljs-type">VNode</span>.html(<span class="hljs-string">"app"</span>)(
  <span class="hljs-type">VMod</span>.style(<span class="hljs-string">"user-select"</span>) := <span class="hljs-string">"none"</span>,
  <span class="hljs-type">VMod</span>.attr(<span class="hljs-string">"everything"</span>) := <span class="hljs-string">"possible"</span>,
  <span class="hljs-type">VMod</span>.prop(<span class="hljs-string">"it"</span>) := <span class="hljs-string">"is"</span>
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run18" data-mdoc-js></div>
<p>There also exists <code>VNode.svg(tagName)</code>.</p>
<p>You can also define the accumulation behavior of custom attributes:</p>
<pre><code class="hljs css language-scala">div(
  <span class="hljs-type">VMod</span>.attr(<span class="hljs-string">"everything"</span>).accum(<span class="hljs-string">"-"</span>) := <span class="hljs-string">"is"</span>,
  <span class="hljs-type">VMod</span>.attr(<span class="hljs-string">"everything"</span>).accum(<span class="hljs-string">"-"</span>) := <span class="hljs-string">"possible"</span>,
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run19" data-mdoc-js></div>
<p>If you think there is something missing in Scala Dom Types, please open a PR or Issue. Usually it's just a few lines of code.</p>
<p>Source Code: <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/definitions/DomTypes.scala">DomTypes.scala</a></p>
<h3><a class="anchor" aria-hidden="true" id="data-and-aria-attributes"></a><a href="#data-and-aria-attributes" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Data and Aria attributes</h3>
<p>Data and aria attributes make use of <a href="https://www.scala-lang.org/api/current/scala/Dynamic.html"><code>scala.Dynamic</code></a>, so you can write things like:</p>
<pre><code class="hljs css language-scala">div(
  data.payload := <span class="hljs-string">"17"</span>,
  data.`consent-required` := <span class="hljs-string">"Are you sure?"</span>,
  data.message.success := <span class="hljs-string">"Message sent!"</span>,
  aria.hidden := <span class="hljs-string">"true"</span>,
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run20" data-mdoc-js></div>
<p>Source Code: <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/definitions/OutwatchAttributes.scala#L75">OutwatchAttributes.scala</a>, <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/helpers/Builder.scala#L35">Builder.scala</a></p>
<h3><a class="anchor" aria-hidden="true" id="svg"></a><a href="#svg" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>SVG</h3>
<p>SVG tags and attributes are available through an extra import. Namespacing is automatically handled for you.</p>
<pre><code class="hljs css language-scala">val component = {
  import svg._
  svg(
    height := "100px",
    viewBox := "0 0 471.701 471.701",
    g(
      path(d := """M433.601,67.001c-24.7-24.7-57.4-38.2-92.3-38.2s-67.7,13.6-92.4,38.3l-12.9,12.9l-13.1-13.1
        c-24.7-24.7-57.6-38.4-92.5-38.4c-34.8,0-67.6,13.6-92.2,38.2c-24.7,24.7-38.3,57.5-38.2,92.4c0,34.9,13.7,67.6,38.4,92.3
        l187.8,187.8c2.6,2.6,6.1,4,9.5,4c3.4,0,6.9-1.3,9.5-3.9l188.2-187.5c24.7-24.7,38.3-57.5,38.3-92.4
        C471.801,124.501,458.301,91.701,433.601,67.001z M414.401,232.701l-178.7,178l-178.3-178.3c-19.6-19.6-30.4-45.6-30.4-73.3
        s10.7-53.7,30.3-73.2c19.5-19.5,45.5-30.3,73.1-30.3c27.7,0,53.8,10.8,73.4,30.4l22.6,22.6c5.3,5.3,13.8,5.3,19.1,0l22.4-22.4
        c19.6-19.6,45.7-30.4,73.3-30.4c27.6,0,53.6,10.8,73.2,30.3c19.6,19.6,30.3,45.6,30.3,73.3
        C444.801,187.101,434.001,213.101,414.401,232.701z""", fill := "tomato")
    )
  )
}

Outwatch.renderInto[SyncIO](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run21" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="vnode-and-vmod"></a><a href="#vnode-and-vmod" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>VNode and VMod</h3>
<p>The important types we are using in the examples above are <code>VNode</code> and <code>VMod</code>. <code>VNode</code> represents a node in the virtual dom, while and <code>VMod</code> represents atrributes and styles and children of a node.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> vnode: <span class="hljs-type">VNode</span> = div()
<span class="hljs-keyword">val</span> modifiers: <span class="hljs-type">List</span>[<span class="hljs-type">VMod</span>] = <span class="hljs-type">List</span>(<span class="hljs-string">"Hello"</span>, idAttr := <span class="hljs-string">"main"</span>, color := <span class="hljs-string">"tomato"</span>, vnode)
</code></pre>
<p>Every <code>VNode</code> contains a sequence of <code>VMod</code>. And a <code>VNode</code> is a <code>VMod</code> itself.</p>
<h3><a class="anchor" aria-hidden="true" id="grouping-modifiers"></a><a href="#grouping-modifiers" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Grouping Modifiers</h3>
<p>To make a set of modifiers reusable you can group them to become one <code>VMod</code>.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> bigFont = <span class="hljs-type">VMod</span>(fontSize := <span class="hljs-string">"40px"</span>, fontWeight.bold)
div(<span class="hljs-string">"Argh!"</span>, bigFont).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run24" data-mdoc-js></div>
<p>If you want to reuse <code>bigFont</code>, but want to overwrite one of its properties, simply append the overwriting modifier. Here the latter <code>fontSize</code> will overwrite the one from <code>bigFont</code>:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> bigFont = <span class="hljs-type">VMod</span>(fontSize := <span class="hljs-string">"40px"</span>, fontWeight.bold)
<span class="hljs-keyword">val</span> bigFont2 = <span class="hljs-type">VMod</span>(bigFont, fontSize := <span class="hljs-string">"99px"</span>)
div(<span class="hljs-string">"Argh!"</span>, bigFont2).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run25" data-mdoc-js></div>
<p>You can also use a <code>Seq[VMod]</code> directly instead of using <code>VMod.apply</code>.</p>
<h3><a class="anchor" aria-hidden="true" id="components"></a><a href="#components" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Components</h3>
<p>Outwatch does not have the concept of a component itself. You can just pass <code>VNode</code>s and <code>VMod</code>s around and build your own abstractions using functions. When we are talking about components in this documentation, we are usually referring to a <code>VNode</code> or a function returning a <code>VNode</code>.</p>
<pre><code class="hljs css language-scala"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fancyHeadLine</span></span>(content: <span class="hljs-type">String</span>) = h1(borderBottom := <span class="hljs-string">"1px dashed tomato"</span>, content)
fancyHeadLine(<span class="hljs-string">"I like tomatoes."</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run26" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="transforming-components"></a><a href="#transforming-components" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Transforming Components</h3>
<p>Components are immutable, we can only modify them by creating a changed copy. Like you may know from Scalatags, you can call <code>.apply(...)</code> on any <code>VNode</code>, <em>append</em> more modifiers and get a new <code>VNode</code> with the applied changes back.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> x = div(<span class="hljs-string">"dog"</span>)
x(title := <span class="hljs-string">"the dog"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run27" data-mdoc-js></div>
<p>This can be useful for reusing html snippets.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> box = div(width := <span class="hljs-string">"100px"</span>, height := <span class="hljs-string">"100px"</span>)

div(
  box(backgroundColor := <span class="hljs-string">"powderblue"</span>),
  box(backgroundColor := <span class="hljs-string">"mediumseagreen"</span>),
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run28" data-mdoc-js></div>
<p>Since modifiers are <em>appended</em>, they can overwrite existing ones. This is useful to adjust existing components to your needs.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> box = div(width := <span class="hljs-string">"100px"</span>, height := <span class="hljs-string">"100px"</span>)
box(backgroundColor := <span class="hljs-string">"mediumseagreen"</span>, width := <span class="hljs-string">"200px"</span>).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run29" data-mdoc-js></div>
<p>You can also <em>prepend</em> modifiers. This can be useful to provide defaults retroactively.</p>
<pre><code class="hljs css language-scala"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">withBorderIfNotProvided</span></span>(vnode: <span class="hljs-type">VNode</span>) = vnode.prepend(border := <span class="hljs-string">"3px solid coral"</span>)
div(
  withBorderIfNotProvided(div(<span class="hljs-string">"hello"</span>, border := <span class="hljs-string">"7px solid moccasin"</span>)),
  withBorderIfNotProvided(div(<span class="hljs-string">"hello"</span>)),
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run30" data-mdoc-js></div>
<p>Source Code: <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/VMod.scala#L110">VMod.scala</a></p>
<h3><a class="anchor" aria-hidden="true" id="example-flexbox"></a><a href="#example-flexbox" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Example: Flexbox</h3>
<p>When working with <a href="https://css-tricks.com/snippets/css/a-guide-to-flexbox/">Flexbox</a>, you can set styles for the <strong>container</strong> and <strong>children</strong>. With <code>VNode.apply()</code> you can have all flexbox-related styles in one place. The child-components don't have to know anything about flexbox, even though they get specific styles assigned.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">val</span> itemA = div(<span class="hljs-string">"A"</span>, backgroundColor := <span class="hljs-string">"mediumseagreen"</span>)
<span class="hljs-keyword">val</span> itemB = div(<span class="hljs-string">"B"</span>, backgroundColor := <span class="hljs-string">"cornflowerblue"</span>)

<span class="hljs-keyword">val</span> component = div(
  height := <span class="hljs-string">"100px"</span>,
  border := <span class="hljs-string">"1px solid black"</span>,

  display.flex,

  itemA(flexBasis := <span class="hljs-string">"50px"</span>),
  itemB(alignSelf.center),
)
component.showHTMLForDoc(docPreview)
<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run31" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="option-and-seq"></a><a href="#option-and-seq" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Option and Seq</h3>
<p>Outwatch can render anything that implements the type class <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/Render.scala"><code>Render</code></a>. Instances for types like <code>Option</code> and <code>Seq</code> are built-in and can be arbitrarily combined:</p>
<pre><code class="hljs css language-scala">div(
  <span class="hljs-type">Some</span>(<span class="hljs-string">"thing"</span>),
  <span class="hljs-type">Some</span>(color := <span class="hljs-string">"steelblue"</span>),
  fontSize := <span class="hljs-type">Some</span>(<span class="hljs-string">"70px"</span>),
  <span class="hljs-type">Seq</span>(<span class="hljs-string">"Hey"</span>, <span class="hljs-string">"How are you?"</span>),
  <span class="hljs-type">List</span>(<span class="hljs-string">"a"</span>, <span class="hljs-string">"b"</span>, <span class="hljs-string">"c"</span>).map(div(_)),
  <span class="hljs-type">Some</span>(<span class="hljs-type">Seq</span>(<span class="hljs-string">"x"</span>)),
).showHTMLForDoc(docPreview)
</code></pre>
<div id="mdoc-html-run32" data-mdoc-js></div>
<p>Note, that outwatch does not accept <code>Set</code>, since the order is undefined.</p>
<h3><a class="anchor" aria-hidden="true" id="rendering-custom-types"></a><a href="#rendering-custom-types" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Rendering Custom Types</h3>
<p>You can render any custom type by implementing the typeclass <code>Render</code>:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">case</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>(<span class="hljs-params">name: <span class="hljs-type">String</span>, age: <span class="hljs-type">Int</span></span>)</span>

<span class="hljs-comment">// Type class instance for `Render`:</span>
<span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">Person</span> </span>{
  <span class="hljs-keyword">implicit</span> <span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">PersonRender</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Render</span>[<span class="hljs-type">Person</span>] </span>{
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render</span></span>(person: <span class="hljs-type">Person</span>): <span class="hljs-type">VMod</span> = div(
      border := <span class="hljs-string">"2px dotted coral"</span>,
      padding := <span class="hljs-string">"10px"</span>,
      marginBottom := <span class="hljs-string">"5px"</span>,
      b(person.name), <span class="hljs-string">": "</span> , person.age
    )
  }
}

<span class="hljs-comment">// Now you can just use instances of `Person` in your dom definitions:</span>
<span class="hljs-keyword">val</span> hans = <span class="hljs-type">Person</span>(<span class="hljs-string">"Hans"</span>, age = <span class="hljs-number">16</span>)
<span class="hljs-keyword">val</span> peter = <span class="hljs-type">Person</span>(<span class="hljs-string">"Peter"</span>, age = <span class="hljs-number">22</span>)

<span class="hljs-keyword">val</span> component = div(hans, peter)

component.showHTMLForDoc(docPreview)
<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run33" data-mdoc-js></div>
<p>Source Code: <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/Render.scala">Render.scala</a></p>
<h2><a class="anchor" aria-hidden="true" id="dynamic-content"></a><a href="#dynamic-content" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dynamic Content</h2>
<h3><a class="anchor" aria-hidden="true" id="reactive-programming"></a><a href="#reactive-programming" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reactive Programming</h3>
<p>Outwatch natively renders reactive data types, like <code>Observable</code> or <code>Rx</code> (Scala.Rx). Outwatch internally uses the <a href="https://github.com/cornerman/colibri"><code>colibri</code></a> library and therefore provides lightweight observables and subjects out of the box. <code>BehaviorSubject</code> (which is also an <code>Observable</code>) is especially useful to deal with state. All subscriptions are automatically handled by outwatch.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> outwatch._
<span class="hljs-keyword">import</span> outwatch.dsl._
<span class="hljs-keyword">import</span> cats.effect.{<span class="hljs-type">IO</span>, <span class="hljs-type">SyncIO</span>}
</code></pre>
<pre><code class="hljs css language-scala"><span class="hljs-comment">// zio</span>
<span class="hljs-keyword">import</span> zio.<span class="hljs-type">Runtime</span>.<span class="hljs-keyword">default</span>
<span class="hljs-keyword">import</span> colibri.ext.zio._

<span class="hljs-comment">// fs2</span>
<span class="hljs-keyword">import</span> cats.effect.unsafe.<span class="hljs-type">IORuntime</span>.global
<span class="hljs-keyword">import</span> colibri.ext.fs2._

<span class="hljs-comment">// airstream</span>
<span class="hljs-keyword">import</span> colibri.ext.airstream._

<span class="hljs-keyword">import</span> scala.concurrent.duration._

<span class="hljs-keyword">val</span> duration = <span class="hljs-number">1.</span>second
<span class="hljs-keyword">val</span> durationMillis = duration.toMillis.toInt
<span class="hljs-keyword">val</span> zioDuration = zio.<span class="hljs-type">Duration</span>.fromScala(<span class="hljs-number">1.</span>second)

<span class="hljs-keyword">val</span> component = {
  div(
    div(
      <span class="hljs-string">"Observable (colibri): "</span>,
      colibri.<span class="hljs-type">Observable</span>.interval(duration),
    ),
    div(
      <span class="hljs-string">"EventStream (airstream): "</span>,
      com.raquo.airstream.core.<span class="hljs-type">EventStream</span>.periodic(intervalMs = durationMillis)
    ),
    div(
      <span class="hljs-string">"Stream (fs2): "</span>,
      fs2.<span class="hljs-type">Stream</span>.awakeDelay[<span class="hljs-type">IO</span>](duration).as(<span class="hljs-number">1</span>).scan[<span class="hljs-type">Int</span>](<span class="hljs-number">0</span>)(_ + _),
    ),
    div(
      <span class="hljs-string">"Stream (zio): "</span>,
      zio.stream.<span class="hljs-type">ZStream</span>.tick(zioDuration).as(<span class="hljs-number">1</span>).scan[<span class="hljs-type">Int</span>](<span class="hljs-number">0</span>)(_ + _),
    )
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run35" data-mdoc-js></div>
<p><strong>Important:</strong> In your application, <code>Outwatch.renderReplace</code> should be called only once at the end of the main method. To create dynamic content, you will design your data-flow with <code>Observable</code>, <code>Subject</code> and/or <code>Scala.Rx</code> and then instantiate outwatch only once with this method call. Before that, no reactive subscriptions will happen.</p>
<h3><a class="anchor" aria-hidden="true" id="reactive-attributes"></a><a href="#reactive-attributes" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reactive attributes</h3>
<p>Attributes can also take dynamic values.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> concurrent.duration._

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> boxWidth = <span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>second).map(i =&gt; <span class="hljs-keyword">if</span>(i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) <span class="hljs-string">"100px"</span> <span class="hljs-keyword">else</span> <span class="hljs-string">"50px"</span>)
  div(
    width &lt;-- boxWidth,
    height := <span class="hljs-string">"50px"</span>,
    backgroundColor := <span class="hljs-string">"cornflowerblue"</span>,
    transition := <span class="hljs-string">"width 0.5s"</span>,
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run36" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="reactive-modifiers-and-vnodes"></a><a href="#reactive-modifiers-and-vnodes" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reactive Modifiers and VNodes</h3>
<p>You can stream any <code>VMod</code> and therefore whole components, attributes, styles, sets of modifiers, and so on:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> concurrent.duration._

<span class="hljs-keyword">val</span> component = {
  div(
    div(
      width := <span class="hljs-string">"50px"</span>,
      height := <span class="hljs-string">"50px"</span>,
      <span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>second)
        .map{ i =&gt; 
          <span class="hljs-keyword">val</span> color = <span class="hljs-keyword">if</span>(i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) <span class="hljs-string">"tomato"</span> <span class="hljs-keyword">else</span> <span class="hljs-string">"cornflowerblue"</span>
          backgroundColor := color
        },
    ),
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run37" data-mdoc-js></div>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> concurrent.duration._

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> nodeStream: <span class="hljs-type">Observable</span>[<span class="hljs-type">VNode</span>] = <span class="hljs-type">Observable</span>(div(<span class="hljs-string">"I am delayed!"</span>)).delay(<span class="hljs-number">5.</span>seconds)
  div(<span class="hljs-string">"Hello "</span>, nodeStream)
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run38" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="rendering-futures"></a><a href="#rendering-futures" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Rendering Futures</h3>
<p>Futures are natively supported too:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> scala.concurrent.<span class="hljs-type">Future</span>
<span class="hljs-keyword">import</span> org.scalajs.macrotaskexecutor.<span class="hljs-type">MacrotaskExecutor</span>.<span class="hljs-type">Implicits</span>._

<span class="hljs-keyword">val</span> component = {
  div(
    <span class="hljs-type">Future</span> { <span class="hljs-number">1</span> + <span class="hljs-number">1</span> },
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run39" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="rendering-async-effects"></a><a href="#rendering-async-effects" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Rendering Async Effects</h3>
<p>You can render any type like <code>cats.effect.IO</code> or <code>zio.Task</code> (using the typeclass <code>colibri.effect.RunEffect</code>). The effect will be run whenever an element is rendered with this modifier. The implementation normally tries to run the effect sync and switches if there is an async boundary (e.g. <code>IO#syncStep</code>). So you can do:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">IO</span>
<span class="hljs-comment">// import cats.effect.unsafe.Runtime.default</span>

<span class="hljs-keyword">import</span> colibri.ext.zio._
<span class="hljs-keyword">import</span> zio.<span class="hljs-type">ZIO</span>
<span class="hljs-comment">// import zio.Runtime.default</span>

div(
  <span class="hljs-type">IO</span> {
    <span class="hljs-comment">// doSomething</span>
    <span class="hljs-string">"result from IO"</span>
  },
  <span class="hljs-type">ZIO</span>.attempt {
    <span class="hljs-comment">// doSomething</span>
    <span class="hljs-string">"result from ZIO"</span>
  }
)
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="rendering-sync-effects"></a><a href="#rendering-sync-effects" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Rendering Sync Effects</h3>
<p>You can render synchronous effects like <code>cats.effect.SyncIO</code> as well (using the typeclass <code>colibri.effect.RunSyncEffect</code>). The effect will be run sync whenever an element is rendered with this modifier. Example:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

div(
  <span class="hljs-type">SyncIO</span> {
    <span class="hljs-comment">// doSomething</span>
    <span class="hljs-string">"result"</span>
  }
)
</code></pre>
<p>Alternatively you can do the following to achieve the same effect:</p>
<pre><code class="hljs css language-scala">div(
  <span class="hljs-type">VMod</span>.eval {
    <span class="hljs-comment">// doSomething</span>
    <span class="hljs-string">"result"</span>
  }
)
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="higher-order-reactiveness"></a><a href="#higher-order-reactiveness" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Higher Order Reactiveness</h3>
<p>Don't fear to nest different reactive constructs. Outwatch will handle everything for you. Example use-cases include sequences of api-requests, live database queries, etc.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> scala.concurrent.<span class="hljs-type">Future</span>
<span class="hljs-keyword">import</span> concurrent.duration._
<span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> cats.effect.{<span class="hljs-type">SyncIO</span>, <span class="hljs-type">IO</span>}

<span class="hljs-keyword">val</span> component = {
  div(
    div(<span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds).map(i =&gt; <span class="hljs-type">Future</span>.successful { i*i })),
    div(<span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds).map(i =&gt; <span class="hljs-type">IO</span> { i*<span class="hljs-number">2</span> })),
    div(<span class="hljs-type">IO</span> { <span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds) }),
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run46" data-mdoc-js></div>
<p>This is effectively the same as:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> scala.concurrent.<span class="hljs-type">Future</span>
<span class="hljs-keyword">import</span> concurrent.duration._
<span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> cats.effect.{<span class="hljs-type">SyncIO</span>, <span class="hljs-type">IO</span>}

<span class="hljs-keyword">val</span> component = {
  div(
    div(<span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds).mapFuture(i =&gt; <span class="hljs-type">Future</span>.successful { i*i })),
    div(<span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds).mapEffect(i =&gt; <span class="hljs-type">IO</span> { i*<span class="hljs-number">2</span> })),
    div(<span class="hljs-type">Observable</span>.interval(<span class="hljs-number">1.</span>seconds)),
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run47" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="using-other-streaming-libraries"></a><a href="#using-other-streaming-libraries" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Using other streaming libraries</h3>
<p>We use the library <a href="https://github.com/cornerman/colibri"><code>colibri</code></a> for a minimal reactive library and for typeclasses around streaming. These typeclasses like <code>Source</code> and <code>Sink</code> allow to integrate third-party libraries for streaming easily.</p>
<p>For using outwatch with zio:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.ext.zio._
</code></pre>
<p>For using outwatch with fs2:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.ext.fs2._
</code></pre>
<p>For using outwatch with airstream:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.ext.airstream._
</code></pre>
<p>For using outwatch with scala.rx (Only Scala 2.x):</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.ext.rx._
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="dom-lifecycle-mangement"></a><a href="#dom-lifecycle-mangement" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dom Lifecycle Mangement</h3>
<p>Outwatch automatically handles subscriptions of streams and observables in your components. You desribe your component with static and dynamic content (subjects, event emitters, <code>--&gt;</code> and <code>&lt;--</code>). When using these components, the needed subscriptions will be tied to the lifecycle of the respective dom elements and are managed for you. So whenever an element is mounted the subscriptions are run and whenever it is unmounted the subscriptions are killed.</p>
<p>If you ever need to manually subscribe to a stream, you can let Outwatch manage the subscription for you:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

div(
  <span class="hljs-type">VMod</span>.managed(myObservable.subscribeF[<span class="hljs-type">IO</span>](???)), <span class="hljs-comment">// this subscription is now bound to the lifetime of the outer div element</span>
  <span class="hljs-type">VMod</span>.managedSubscribe(myObservable.tapEffect(x =&gt; ???).void) <span class="hljs-comment">// this subscription is now bound to the lifetime of the outer div element</span>
)
</code></pre>
<h2><a class="anchor" aria-hidden="true" id="handling-events"></a><a href="#handling-events" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Handling Events</h2>
<p>Outwatch allows to react to dom events in a very flexible way. Every event handler emits events to be further processed. Events can trigger side-effects, be filtered, mapped, replaced or forwarded to reactive variables. In outwatch this concept is called <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/EmitterBuilder.scala">EmitterBuilder</a>. For example, take the <code>click</code> event for which <code>onClick</code> is an <code>EmitterBuilder</code>:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> org.scalajs.dom.console

<span class="hljs-keyword">val</span> component = {
  div(
    button(
      <span class="hljs-string">"Log a Click-Event"</span>,
      onClick.foreach { e =&gt; console.log(<span class="hljs-string">"Event: "</span>, e) },
    ),
    <span class="hljs-string">" (Check the browser console to see the event)"</span>
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run54" data-mdoc-js></div>
<p>In the next example, we map the event <code>e</code> to extract <code>e.clientX</code> and write the result into the reactive variable (<code>BehaviorSubject</code>) called <code>x</code>:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>
<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> x = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-number">0.0</span>)
  div(
      div(
        <span class="hljs-string">"Hover to see mouse x-coordinate"</span>,
        onMouseMove.map(e =&gt; e.clientX) --&gt; x,
        backgroundColor := <span class="hljs-string">"lightpink"</span>,
        cursor.crosshair,
      ),
      div(<span class="hljs-string">" x = "</span>, x )
   )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run55" data-mdoc-js></div>
<p><code>EmitterBuilder</code> comes with many useful methods to make your life easier. Check the completions of your editor.</p>
<h3><a class="anchor" aria-hidden="true" id="example-counter"></a><a href="#example-counter" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Example: Counter</h3>
<p>We don't have to use the dom-event at all. Often it's useful to replace it with a constant or the current value of a reactive variable. Let's revisit the counter example:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>
<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> counter = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-number">0</span>)
  div(
      button(<span class="hljs-string">"increase"</span>, onClick(counter.map(_ + <span class="hljs-number">1</span>)) --&gt; counter),
      button(<span class="hljs-string">"decrease"</span>, onClick(counter.map(_ - <span class="hljs-number">1</span>)) --&gt; counter),
      button(<span class="hljs-string">"reset"</span>, onClick.as(<span class="hljs-number">0</span>) --&gt; counter),
      div(<span class="hljs-string">"counter: "</span>, counter )
   )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run56" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="example-input-field"></a><a href="#example-input-field" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Example: Input field</h3>
<p>Another common use-case is the handling of values of input fields. Here the <code>BehaviorSubject</code> called <code>text</code> reflects the current value of the input field. On every keystroke, <code>onInput</code> emits an event, <code>.value</code> extracts the string from the input field and writes it into <code>text</code>. But the <code>value</code> attribute of the input field also reacts on changes to the reactive variable, which we use to clear the text field. We have a second reactive variable that holds the submitted value after pressing enter.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>
<span class="hljs-keyword">import</span> org.scalajs.dom.ext.<span class="hljs-type">KeyCode</span>

<span class="hljs-comment">// Emitterbuilders can be extracted and reused!</span>
<span class="hljs-keyword">val</span> onEnter = onKeyDown
    .filter(e =&gt; e.keyCode == <span class="hljs-type">KeyCode</span>.<span class="hljs-type">Enter</span>)
    .preventDefault

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> text = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-string">""</span>)
  <span class="hljs-keyword">val</span> submitted = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-string">""</span>)
  div(
      input(
        tpe := <span class="hljs-string">"text"</span>,
        value &lt;-- text,
        onInput.value --&gt; text,
        onEnter(text) --&gt; submitted,
      ),
      button(<span class="hljs-string">"clear"</span>, onClick.as(<span class="hljs-string">""</span>) --&gt; text),
      div(<span class="hljs-string">"text: "</span>, text),
      div(<span class="hljs-string">"length: "</span>, text.map(_.length)),
      div(<span class="hljs-string">"submitted: "</span>, submitted),
   )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run57" data-mdoc-js></div>
<p>Another example with <code>debounce</code> functionality. The <code>debounced</code> reactive variable is filled once you stop typing for 500ms.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>
<span class="hljs-keyword">import</span> org.scalajs.dom.ext.<span class="hljs-type">KeyCode</span>

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> text = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-string">""</span>)
  <span class="hljs-keyword">val</span> debounced = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-string">""</span>)
  div(
      input(
        tpe := <span class="hljs-string">"text"</span>,
        onInput.value --&gt; text,
        onInput.value.debounceMillis(<span class="hljs-number">500</span>) --&gt; debounced,
      ),
      div(<span class="hljs-string">"text: "</span>, text),
      div(<span class="hljs-string">"debounced: "</span>, debounced),
   )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run58" data-mdoc-js></div>
<h3><a class="anchor" aria-hidden="true" id="global-events"></a><a href="#global-events" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Global events</h3>
<p>There are helpers for getting streams of global <code>document</code> or <code>window</code> events as a handy <code>colibri.Observable</code>:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>
<span class="hljs-keyword">import</span> org.scalajs.dom.document

<span class="hljs-keyword">val</span> component = {
  <span class="hljs-keyword">val</span> onBlur = events.window.onBlur.map(_ =&gt; <span class="hljs-string">"blur"</span>)
  <span class="hljs-keyword">val</span> onFocus = events.window.onFocus.map(_ =&gt; <span class="hljs-string">"focus"</span>)
  div(
    div(<span class="hljs-string">"document.onKeyDown: "</span>, events.document.onKeyDown.map(_.key)),
    div(<span class="hljs-string">"window focus: "</span>, <span class="hljs-type">Observable</span>.merge(onBlur, onFocus))
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run59" data-mdoc-js></div>
<h2><a class="anchor" aria-hidden="true" id="referential-transparency"></a><a href="#referential-transparency" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Referential transparency</h2>
<p>When looking at our counter component, you might have noticed that the reactive variable <code>number</code> is instantiated immediately. It belongs to our component value <code>counter</code>. Let's see what happens when we're using this component twice:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>

<span class="hljs-keyword">val</span> counter = {
  <span class="hljs-keyword">val</span> number = <span class="hljs-type">Subject</span>.behavior(<span class="hljs-number">0</span>)
  div(
      button(number, onClick(number.map(_ + <span class="hljs-number">1</span>)) --&gt; number),
   )
}

<span class="hljs-keyword">val</span> component = {
  div(
    <span class="hljs-string">"Counters sharing state:"</span>,
    counter,
    counter
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run60" data-mdoc-js></div>
<p>As you can see, the state is shared between all usages of the component. Therefore, the component counter is not referentially transparent. We can change this, by wrapping the component in <code>IO</code> (or here: <code>SyncIO</code>). With this change, the reactive variable <code>number</code> is instantiated separately for every usage at rendering time:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Subject</span>
<span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

<span class="hljs-keyword">val</span> counter = <span class="hljs-keyword">for</span> {
  number &lt;- <span class="hljs-type">SyncIO</span>(<span class="hljs-type">Subject</span>.behavior(<span class="hljs-number">0</span>)) <span class="hljs-comment">// &lt;-- added IO</span>
} <span class="hljs-keyword">yield</span> div(
    number,
    button(number, onClick(number.map(_ + <span class="hljs-number">1</span>)) --&gt; number),
)

<span class="hljs-keyword">val</span> component = {
  div(
    <span class="hljs-string">"Referentially transparent counters:"</span>,
    counter,
    counter
  )
}

<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run61" data-mdoc-js></div>
<p>Why should we care? Because referentially transparent components can easily be refactored without affecting the meaning of the program. Therefore it is easier to reason about them. Read more about the concept in Wikipedia: <a href="https://en.wikipedia.org/wiki/Referential_transparency">Referential transparency</a></p>
<h2><a class="anchor" aria-hidden="true" id="advanced"></a><a href="#advanced" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Advanced</h2>
<h3><a class="anchor" aria-hidden="true" id="accessing-the-dom-element"></a><a href="#accessing-the-dom-element" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Accessing the DOM Element</h3>
<p>Sometimes you need to access the underlying DOM element of a component. But a VNode in Outwatch is just a description of a dom element and we can create multiple different elements from one VNode. Therefore, there is no static element attached to a component. Though, you can get access to the dom element via hooks (callbacks):</p>
<pre><code class="hljs css language-scala">div(
  onDomMount.foreach { element =&gt; <span class="hljs-comment">// the element into which this node is mounted</span>
      ???
  },
  onDomUnmount.foreach { element =&gt; <span class="hljs-comment">// the element from which this node is unmounted</span>
      ???
  }
)
</code></pre>
<p>Outwatch has a higher-level API to work with these kinds of callbacks, called <code>VMod.managedElement</code>, which can be used like this:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Cancelable</span>

div(
  <span class="hljs-type">VMod</span>.managedElement { element =&gt; <span class="hljs-comment">// the element into which this node is mounted</span>
    ??? <span class="hljs-comment">// do something with the dom element</span>
    <span class="hljs-type">Cancelable</span>(() =&gt; ???) <span class="hljs-comment">// cleanup when the dom element is unmounted</span>
  }
)
</code></pre>
<p>You can also get the current element when handling dom events, for example onClick:</p>
<pre><code class="hljs css language-scala">div(
  onClick.asElement.foreach { element =&gt;
    ???
  } <span class="hljs-comment">// this is the same as onClick.map(_.currentTarget)</span>
)
</code></pre>
<p>If the emitter does not emit events or elements, but you still want to access the current element, you can combine it with another emitter. For example:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Observable</span>

<span class="hljs-keyword">val</span> someObservable:<span class="hljs-type">Observable</span>[<span class="hljs-type">Int</span>] = ???
div(
  <span class="hljs-type">EmitterBuilder</span>.fromSource(someObservable).asLatestEmitter(onDomMount).foreach { element =&gt;
    ???
    ()
  }
)
</code></pre>
<p>If you need an HTML or SVG Element instead of just an Element, you can do:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> org.scalajs.dom._
<span class="hljs-keyword">import</span> colibri.<span class="hljs-type">Cancelable</span>

onDomMount.asHtml.foreach{ (elem: html.<span class="hljs-type">Element</span>) =&gt; ??? }
onDomMount.asSvg.foreach{ (elem: svg.<span class="hljs-type">Element</span>) =&gt; ??? }
onClick.asHtml.foreach{ (elem: html.<span class="hljs-type">Element</span>) =&gt; ??? }
onClick.asSvg.foreach{ (elem: svg.<span class="hljs-type">Element</span>) =&gt; ??? }
<span class="hljs-type">VMod</span>.managedElement.asHtml { (elem: html.<span class="hljs-type">Element</span>) =&gt; ???; <span class="hljs-type">Cancelable</span>(() =&gt; ???) }
<span class="hljs-type">VMod</span>.managedElement.asSvg { (elem: svg.<span class="hljs-type">Element</span>) =&gt; ???; <span class="hljs-type">Cancelable</span>(() =&gt; ???) }
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="custom-emitterbuilders"></a><a href="#custom-emitterbuilders" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Custom EmitterBuilders</h3>
<p>You can <code>combine</code>, <code>map</code>, <code>collect</code>, <code>filter</code> and <code>transform</code> <code>EmitterBuilder</code>:</p>
<pre><code class="hljs css language-scala">button(<span class="hljs-type">EmitterBuilder</span>.combine(onMouseUp.as(<span class="hljs-literal">false</span>), onMouseDown.as(<span class="hljs-literal">true</span>)).foreach { isDown =&gt; println(<span class="hljs-string">"Button is down? "</span> + isDown) })
<span class="hljs-comment">// this is the same as: button(EmitterBuilder.combine(onMouseUp.map(_ =&gt; false), onMouseDown.map(_ =&gt; true)).foreach { isDown =&gt; println("Button is down? " + isDown })</span>
</code></pre>
<p>Furthermore, you can create EmitterBuilders from streams with <code>EmitterBuilder.fromSource</code> or create custom EmitterBuilders with <code>EmitterBuilder.ofModifier</code>, <code>EmitterBuilder.ofNode</code> or <code>EmitterBuilder.apply</code>.</p>
<h2><a class="anchor" aria-hidden="true" id="debugging"></a><a href="#debugging" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Debugging</h2>
<p>Source Code: <a href="https://github.com/outwatch/outwatch/blob/master/outwatch/src/main/scala/outwatch/helpers/OutwatchTracing.scala">OutwatchTracing.scala</a></p>
<h3><a class="anchor" aria-hidden="true" id="tracing-snabbdom-patches"></a><a href="#tracing-snabbdom-patches" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tracing snabbdom patches</h3>
<p>Show which patches snabbdom emits:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> scala.scalajs.js.<span class="hljs-type">JSON</span>
<span class="hljs-keyword">import</span> org.scalajs.dom.console

helpers.<span class="hljs-type">OutwatchTracing</span>.patch.zipWithIndex.unsafeForeach { <span class="hljs-keyword">case</span> (proxy, index) =&gt;
  console.log(<span class="hljs-string">s"Snabbdom patch (<span class="hljs-subst">$index</span>)!"</span>, <span class="hljs-type">JSON</span>.parse(<span class="hljs-type">JSON</span>.stringify(proxy)), proxy)
}
</code></pre>
<h3><a class="anchor" aria-hidden="true" id="tracing-exceptions-in-your-components"></a><a href="#tracing-exceptions-in-your-components" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tracing exceptions in your components</h3>
<p>Dynamic components with <code>Observables</code> can have errors. This happens if <code>onError</code> is called on the underlying <code>Observer</code>. Same for <code>IO</code> when it fails. In these cases, Outwatch will always print an error message to the dom console.</p>
<p>Furthermore, you can configure whether Outwatch should render errors to the dom by providing a <code>RenderConfig</code>. <code>RenderConfig.showError</code> always shows errors, <code>RenderConfig.ignoreError</code> never shows errors and <code>RenderConfig.default</code> only shows errors when running on <code>localhost</code>.</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

<span class="hljs-keyword">val</span> component = div(<span class="hljs-string">"broken?"</span>, <span class="hljs-type">SyncIO</span>.raiseError[<span class="hljs-type">VMod</span>](<span class="hljs-keyword">new</span> <span class="hljs-type">Exception</span>(<span class="hljs-string">"I am broken"</span>)))
<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component, config = <span class="hljs-type">RenderConfig</span>.showError).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run76" data-mdoc-js></div>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> cats.effect.<span class="hljs-type">SyncIO</span>

<span class="hljs-keyword">val</span> component = div(<span class="hljs-string">"broken?"</span>, <span class="hljs-type">SyncIO</span>.raiseError[<span class="hljs-type">VMod</span>](<span class="hljs-keyword">new</span> <span class="hljs-type">Exception</span>(<span class="hljs-string">"I am broken"</span>)))
<span class="hljs-type">Outwatch</span>.renderInto[<span class="hljs-type">SyncIO</span>](docPreview, component, config = <span class="hljs-type">RenderConfig</span>.ignoreError).unsafeRunSync()
</code></pre>
<div id="mdoc-html-run77" data-mdoc-js></div>
<p>You can additionally trace and react to these errors in your own code:</p>
<pre><code class="hljs css language-scala"><span class="hljs-keyword">import</span> org.scalajs.dom.console

helpers.<span class="hljs-type">OutwatchTracing</span>.error.unsafeForeach { throwable =&gt;
  console.log(<span class="hljs-string">s"Exception while patching an Outwatch compontent: <span class="hljs-subst">${throwable.getMessage}</span>"</span>)
}
</code></pre>
<h2><a class="anchor" aria-hidden="true" id="improving-the-documentation"></a><a href="#improving-the-documentation" aria-hidden="true" class="hash-link"><svg class="hash-link-icon" aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Improving the Documentation</h2>
<p>This documentation is written using <a href="https://github.com/scalameta/mdoc">mdoc</a>. The markdown file is located in <a href="https://github.com/outwatch/outwatch/blob/master/docs/readme.md">docs/readme.md</a>.</p>
<p>To get a live preview of the code examples in the browser:</p>
<p>Clone the outwatch repo:</p>
<pre><code class="hljs css language-bash">git <span class="hljs-built_in">clone</span> git@github.com:outwatch/outwatch.git
</code></pre>
<p>Run mdoc:</p>
<pre><code class="hljs"><span class="hljs-keyword">sbt </span><span class="hljs-string">"docs/mdoc --watch"</span>
</code></pre>
<p>Point your browser to: <a href="http://localhost:4000/readme.md">http://localhost:4000/readme.md</a></p>
<p>And edit <code>docs/readme.md</code>.</p>
<p>Thank you!</p>
<script type="text/javascript" src="assets/jsdocs-opt-library.js" defer></script>
<script type="text/javascript" src="assets/jsdocs-opt-loader.js" defer></script>
<script type="text/javascript" src="assets/readme.md.js" defer></script>
<script type="text/javascript" src="assets/mdoc.js" defer></script>
</span></div></article></div><div class="docs-prevnext"></div></div></div><nav class="onPageNav"><ul class="toc-headings"><li><a href="#getting-started">Getting started</a><ul class="toc-headings"><li><a href="#start-with-a-template">Start with a template</a></li><li><a href="#create-a-project-from-scratch">Create a project from scratch</a></li><li><a href="#use-common-javascript-libraries-with-outwatch">Use common javascript libraries with Outwatch</a></li><li><a href="#convert-html-to-outwatch">Convert html to outwatch</a></li></ul></li><li><a href="#examples">Examples</a></li><li><a href="#hello-world">Hello World</a></li><li><a href="#interactive-counter">Interactive Counter</a></li><li><a href="#static-content">Static Content</a><ul class="toc-headings"><li><a href="#imports">Imports</a></li><li><a href="#concatenating-strings">Concatenating Strings</a></li><li><a href="#nesting">Nesting</a></li><li><a href="#primitives">Primitives</a></li><li><a href="#attributes">Attributes</a></li><li><a href="#styles">Styles</a></li><li><a href="#reserved-scala-keywords-class-for-type">Reserved Scala keywords: class, for, type</a></li><li><a href="#overriding-attributes">Overriding attributes</a></li><li><a href="#css-class-accumulation">CSS class accumulation</a></li><li><a href="#custom-attributes-styles-and-tags">Custom attributes, styles and tags</a></li><li><a href="#data-and-aria-attributes">Data and Aria attributes</a></li><li><a href="#svg">SVG</a></li><li><a href="#vnode-and-vmod">VNode and VMod</a></li><li><a href="#grouping-modifiers">Grouping Modifiers</a></li><li><a href="#components">Components</a></li><li><a href="#transforming-components">Transforming Components</a></li><li><a href="#example-flexbox">Example: Flexbox</a></li><li><a href="#option-and-seq">Option and Seq</a></li><li><a href="#rendering-custom-types">Rendering Custom Types</a></li></ul></li><li><a href="#dynamic-content">Dynamic Content</a><ul class="toc-headings"><li><a href="#reactive-programming">Reactive Programming</a></li><li><a href="#reactive-attributes">Reactive attributes</a></li><li><a href="#reactive-modifiers-and-vnodes">Reactive Modifiers and VNodes</a></li><li><a href="#rendering-futures">Rendering Futures</a></li><li><a href="#rendering-async-effects">Rendering Async Effects</a></li><li><a href="#rendering-sync-effects">Rendering Sync Effects</a></li><li><a href="#higher-order-reactiveness">Higher Order Reactiveness</a></li><li><a href="#using-other-streaming-libraries">Using other streaming libraries</a></li><li><a href="#dom-lifecycle-mangement">Dom Lifecycle Mangement</a></li></ul></li><li><a href="#handling-events">Handling Events</a><ul class="toc-headings"><li><a href="#example-counter">Example: Counter</a></li><li><a href="#example-input-field">Example: Input field</a></li><li><a href="#global-events">Global events</a></li></ul></li><li><a href="#referential-transparency">Referential transparency</a></li><li><a href="#advanced">Advanced</a><ul class="toc-headings"><li><a href="#accessing-the-dom-element">Accessing the DOM Element</a></li><li><a href="#custom-emitterbuilders">Custom EmitterBuilders</a></li></ul></li><li><a href="#debugging">Debugging</a><ul class="toc-headings"><li><a href="#tracing-snabbdom-patches">Tracing snabbdom patches</a></li><li><a href="#tracing-exceptions-in-your-components">Tracing exceptions in your components</a></li></ul></li><li><a href="#improving-the-documentation">Improving the Documentation</a></li></ul></nav></div><footer class="nav-footer" id="footer"><section class="sitemap"><a href="/" class="nav-home"></a><div><a href="https://gitter.im/outwatch/Lobby">Gitter Chat</a></div><div><a href="https://github.com/outwatch/outwatch">GitHub</a><a class="github-button" href="https://github.com/outwatch/outwatch" data-icon="octicon-star" data-count-href="/outwatch/outwatch/stargazers" data-show-count="true" data-count-aria-label="# stargazers on GitHub" aria-label="Star this project on GitHub">Star</a></div></section><section class="copyright">Copyright © 2023 Outwatch contributors</section></footer></div></body></html>

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published