README.md

# rondo [![Build Status](https://travis-ci.org/exstruct/rondo.svg?branch=master)](https://travis-ci.org/exstruct/rondo) [![Hex.pm](https://img.shields.io/hexpm/v/rondo.svg?style=flat-square)](https://hex.pm/packages/rondo) [![Hex.pm](https://img.shields.io/hexpm/dt/rondo.svg?style=flat-square)](https://hex.pm/packages/rondo)

component rendering library

## Installation

`Rondo` is [available in Hex](https://hex.pm/docs/publish) and can be installed as:

  1. Add rondo your list of dependencies in `mix.exs`:

```elixir
def deps do
  [{:rondo, "~> 0.1.0"}]
end
```

## Usage

Start by creating a store

```elixir
defmodule MyApp.Store do
  defstruct [stores: %{}]

  defimpl Rondo.State.Store do
    def mount(%{stores: stores} = store, %{props: initial, type: :ephemeral} = descriptor) do
      stores = Map.put_new(stores, descriptor, initial)
      {Map.get(stores, descriptor), %{store | stores: stores}}
    end

    def handle_info(store, _info) do
      store
    end

    def handle_action(store, descriptor, update_fn) do
      {prev, store} = mount(store, descriptor)
      value = update_fn.(prev)
      stores = Map.put(store.stores, descriptor, value)
      {:ok, %{store | stores: stores}}
    end

    def encode(%{stores: stores}) do
      stores
      |> :erlang.term_to_binary()
      |> Base.url_encode64()
    end

    def decode_into(store, bin) do
      stores = bin
      |> Base.url_decode64!()
      |> :erlang.binary_to_term()
      %{store | stores: stores}
    end
  end
end
```

Now define an action with an affordance (in [jsonschema](http://json-schema.org/) format)

```elixir
defmodule MyApp.Action.Increment do
  def affordance(_props) do
    %{
      "type" => ["integer", "null"]
    }
  end

  def action(_props, state, input) do
    state + (input || 0)
  end
end
```

And a component to use the action

```elixir
defmodule MyApp.Component do
  use Rondo.Component
  alias MyApp.Action.Increment

  def state(_props, _context) do
    %{
      counter: create_store(0)
    }
  end

  def render(%{counter: counter}) do
    el("Text", %{
      "increment" => ref([:counter]) |> action(Increment)
    }, [
      counter
    ])
  end
end
```

We can now render the app

```elixir
# Set up our store and app
store = %MyApp.Store{}
app = Rondo.create_application(MyApp.Component)

# Perform the initial render
{app, store} = Rondo.render(app, store)

# Assert that the counter is initialized and being sent as the first child
{:ok, 0} = Rondo.Test.fetch_path(app, [0])

# We need to get the action ref to submit a form
{:ok, %{props: %{"increment" => %{ref: action_ref}}}} =
  Rondo.Test.fetch_path(app, [])

# If we submit an invalid action it should let us know
{:invalid, errors, app, store} =
  Rondo.submit_action(app, store, action_ref, "Invalid type")

# We now submit a valid action and it goes through
{:ok, app, store} =
  Rondo.submit_action(app, store, action_ref, 1)

# Trigger a render
{app, store} = Rondo.render(app, store)

# We now have the incremented value!
{:ok, 1} = Rondo.Test.fetch_path(app, [0])
```