Narabe

Narabe — Lists, trees and boards for Phoenix

並べ (narabe): to line up, to put in order.

Lists, trees and boards that people reorder, for Phoenix LiveView, with the order kept in your database through Ecto.

<.sortable_list id="tasks" items={@streams.tasks} on_move="move" multiple>
  <:item :let={task}>{task.title}</:item>
</.sortable_list>

<.tree id="pages" items={@pages} rules={Page} on_move="move" on_rename="rename" filter />

<.board id="sprint" columns={@columns} cards={@cards} on_move="move_card" move_columns>
  <:card :let={card}>{card.title}</:card>
</.board>
def handle_event("move", params, socket) do
  result = Narabe.Ecto.move(Repo, where(Page, space_id: ^socket.assigns.space.id), Narabe.Move.parse!(params, Page))
  {:reply, Narabe.Live.reply(result), load_pages(socket)}
end

What it does

The bundle is 12 KB gzipped, the style sheet 4 KB.

Install

Narabe needs Elixir 1.18 or later, Phoenix 1.7.14 or 1.8, Phoenix LiveView 1.1 or later, and Ecto 3.13 or later (ecto_sql for the database helpers).

def deps do
  [
    {:narabe, "~> 0.1"}
  ]
end
mix deps.get
mix narabe.install

The installer adds the hook to assets/js/app.js and the style sheet to assets/css/app.css, and prints what it changed (--dry-run shows it first). By hand:

import { Narabe } from "narabe"

const liveSocket = new LiveSocket("/live", Socket, {
  params: {_csrf_token: csrfToken},
  hooks: {...colocatedHooks, Narabe},
})
@import "../../deps/narabe/priv/static/narabe.css";
/* @import "../../deps/narabe/priv/static/narabe-sumi.css"; */

import { Narabe } from "narabe" resolves through the NODE_PATH of your esbuild profile, which must have deps/ (the Phoenix generators set it).

Use

A position column, and the order on the schema:

# In a migration
import Narabe.Migration

create table(:pages) do
  add :title, :string
  add :parent_id, references(:pages)
  position()
end

create index(:pages, [:parent_id, :position])
schema "pages" do
  field :title, :string
  field :position, :string
  belongs_to :parent, Page
end

use Narabe.Ordered, group: :parent_id, tree: true, rules: [max_depth: 6]

New rows get a position with Narabe.Ecto.insert_position/4 (or put_position/4 in a changeset); Narabe.Ecto.ordered/2 reads them in order. See the Database guide.

Guides

Development

mix deps.get
mix narabe.build   # the bundle and the style sheets, into priv/static
mix dev            # a list, a tree, a board and a picker at http://localhost:4097

Tests

mix test                   # Elixir, on Postgres, MySQL and SQLite
NARABE_DBS=sqlite mix test # some of the databases (none: "none")
mix test --include build   # the build and the package
mix test.js                # the browser's keys, rules and drop places
mix test.e2e               # in Chromium, Firefox and WebKit
MIX_ENV=test mix run bench/ecto.exs  # the database benchmark

The tests expect Postgres at postgres:postgres@localhost and MySQL at narabe:narabe@127.0.0.1 (or NARABE_POSTGRES_URL and NARABE_MYSQL_URL). The position keys and the rules that both the server and the browser apply are tested on both sides with one fixture each: test/fixtures/position.json and test/fixtures/rules.json.

Release

mix release builds the bundle, checks priv/static and the version of package.json, publishes to Hex and tags the version.

Contributing

See CONTRIBUTING.md. Security problems: SECURITY.md.

License

MIT. See LICENSE and NOTICE.