π² π² π²
π²π² π²π² π² π² π² π² π² π² π² π²π² π²
π²π²π² π²π²π² π² π²π²π² π²π² π² π²π²π² π²π²π² π² π²π² π²π² π²
Drag-and-drop interface to edit, dissect and-rearrange tree structures, with nodes holding any data type you wish. Here is a demo, and here some docs.
TLDR: simple app example (run with cd examples && elm reactor
, then open http://localhost:8000/Simple.elm)
First things first, we need to specify what kind of data structure our tree's nodes will hold. For this demo, it will be a record with a question and an answer.
type alias MyNode =
{ question : String
, answer : String
}
exampleNode = MyNode "How are you?" "Fine thanks"
We can then use the Arborist.Tree
module to recursively construct a tree structure:
import Arborist.Tree as Tree
tree : Tree.Tree MyNode
tree =
Tree.Node (MyNode "Q1" "A1")
[ Tree.Node (MyNode "Q2" "A2") []
, Tree.Node (MyNode "Q3" "A3")
[ Tree.Node ( "Q4", "A4" )
]
]
Now, we can now define a model for your app:
import Arborist
import Arborist.Tree as Tree
type alias Model =
{ tree : Tree.Tree MyNode
, arborist : Arborist.State
}
Next, we configure the editor:
arboristSettings : List (Arborist.Setting MyNode)
arboristSettings =
[ Settings.keyboardNavigation True
, Settings.defaultNode (MyNode "A" "Q")
, Settings.nodeWidth 100
, Settings.level 80
, Settings.gutter 20
]
Rendering the editor will look like this:
view : Model -> Html Msg
view model =
Arborist.view
[]
{ state = model.arborist
, tree = model.tree
, settings = arboristSettings
-- We get to these two in a second
, nodeView = nodeView
, toMsg = Arborist
}
Now let's look at the two bits we left out: nodeView
and toMsg
.
This function specifies how a node should be displayed within its bounding box, and looks like this:
-- Don't worry about `context` for now
nodeView : Arborist.NodeView MyNode Msg
nodeView context maybeNode =
case maybeNode of
Just node ->
text node.question
Nothing ->
text "+ add node"
Arborist uses the teach-me-how-to-message pattern, passing an Arborist.Updater
to your app's update
function. The updater is a function that works out the new tree and new state based on the previous tree and previous state.
type Msg
-- This is the message constructor that goes into the `toMsg` field above
= Arborist (Arborist.Updater MyNode)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Arborist updater ->
let
( newState, newTree ) =
updater model.arborist model.tree
in
( { model
| arborist = newState
, tree = newTree
}
, Cmd.none
)
Arborist cannot give new values straight away because mouse events are fired so quickly that they can undo each others' changes in the runtime, hence the complexity in the update logic above.
And that's it - your very own tree editor is ready.
The context object exposed in the nodeView
function above provides, as its name suggests, contextual information to the node when it is rendered, including their parent node and list of siblings. You may for instance want to signal to the user that a child can't be older than their parent in the family tree as they edit it, but traversing the tree to find that information is tedious and inefficient - so Arborist gives you access to it directly.
This should work for a large number of tree editing cases. If you need a broader context, you will need to traverse the tree yourself.
In the settings module, you will find setup instructions for advanced features like node clustering and keyboard navigation. For a real-world example, have a look at the elm-arborist landing page.
Contributions welcome - please feel free to go ahead with issues and PR's, or reach out to me on Elm Slack at @peterszerzo
.
MIT.
π²π²π² π²π²π² π² π²π²π² π²π² π² π²π²π² π²π²π² π² π²π² π²π² π²
π²π² π²π² π² π² π² π² π² π² π² π²π² π²
π² π² π²