Live Demo

Simple Counter

This is about as small demo as possible:

Source / Open in New Tab

The template:

import {Counter, counter} from './counter'

view main():
  <p>
    store @cnt = Counter

    <button>
       link {click} counter(1) -> @cnt
       "+"

    <input disabled size="6" value=@cnt>

    <button>
       link {click} counter(-1) -> @cnt
       "-"

Bootstrap code (index.js):

import {createStore, applyMiddleware} from 'redux'
import {attach} from 'khufu-runtime'
import {main} from './counter.khufu'

attach(document.getElementById('app'), main(), {
    store(reducer, middleware, state) {
        return createStore(reducer, state,
            applyMiddleware(...middleware))
    }
})

And the counter store counter.js.

export function Counter(state=0, action) {
    switch(action.type) {
        case 'incr':
            return state + action.value;
        default:
            return state;
    }
}

export function counter(x) {
    return { type: 'incr', value: x }
}

This is just simple redux store and an action creator.

Debounce

This example demonstrates use of redux-saga for debouncing the event stream:

Source / Open in New Tab

This example shows scoping of stores and how to define stores and middlewares dynamically:

  <div.table>
    for ms of [0, 200, 1000]:
      <div.row>
        store @value = Text | delay_saga(ms)
        <div.main>
          store @id = id
          <label for=@id>
            "Delay "
            ms
            " ms: "
          <input id=@id type="text">
            link {blur, change, keyup, keydown} delay(update(this.value)) -> @value

        <div.clone>
          store @id = id
          <label.arrow for=@id>
            "⤷"
          <input id=@id disabled value=@value>

And here is a delay_saga middleware:

        console.log("VALUE")
        while(true) {
            let action = yield take('delay')
            let deadline = Date.now() + msec
            let diff
            while ((diff = deadline - Date.now()) > 0) {
                let {event} = yield race({
                    event: take('delay'),
                    timeout: sleep(diff),
                })
                if(event) action = event;
            }
            yield put(action.action)
        }
    }
    return {saga: debounce}
}

This looks more complex than stream.debounce(x) from RxJS, but it’s because we don’t use any function defined by library, but rather implementing functionality ourselves. Sure you can use redux-rx or any other stuff. It’s just a demo, khufu does not require to use redux-saga.

Components

Here is a simple demo for a component label that has a body and a badge placeholders:

Source / Open in New Tab

Template code is as simple as:

view label(){body, badge}:
  <div.label>
    body()
    if badge:
      <div.badge> badge()

view main():
  label():
    body: "Inbox"
    badge: 10
  label()
    "Sent mails"

Error Handling

Following is a demo of error handling:

Source / Open in New Tab

Note, how setting “bad value” breaks the rendering immediately. But setting a good value doesn’t do, unless retry action is also executed. This is because the code looks like this (stripped some details:

<div>
  store @fruits = Fruits
  store @has_error = Flag
  if not @has_error:
    catch * set_true() -> @has_error:
      <b> @fruits.banana.price
  else:
    <button>
      link {click} set_false() -> @has_error
      "Retry"

  <button>
    link {click} good_value() -> @fruits
    "Set good value"

I.e. if @fruits store is updated, and @has_error still contains true the catch block isn’t rerendered, but obviously if there was no error and store value changed, the template will rerender and drop into an errorneous state.