Skip to content

Commit

Permalink
[SimplePrimitives] Fix Incremental.accordion
Browse files Browse the repository at this point in the history
Accordion with dynamic content list is weird and broken, but
titles of sections must be adaptive.
  • Loading branch information
hyazinthh committed Mar 18, 2024
1 parent aaa5f54 commit b2e0de0
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 65 deletions.
91 changes: 58 additions & 33 deletions src/Aardvark.UI.Primitives/Primitives/SimplePrimitives.fs
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,23 @@ module SimplePrimitives =
)
)

let private accordionImpl (toggle: Option<bool -> int -> 'msg>) (active: Choice<aset<int>, aval<int>>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
[<RequireQualifiedAccess>]
type private AccordionInput<'msg> =
| Multi of active: aset<int> * callback: (bool -> int -> 'msg)
| Single of active: aval<int> * callback: (bool -> int -> 'msg)
| Empty of exclusive: bool

member inline x.Callback =
match x with
| Multi (_, cb) | Single (_, cb) -> Some cb
| _ -> None

member inline x.IsExclusive =
match x with
| Single _ | Empty true -> true
| _ -> false

let private accordionImpl (input: AccordionInput<'msg>) (attributes: AttributeMap<'msg>) (sections: list<aval<string> * DomNode<'msg>>) =
let dependencies =
Html.semui @ [ { name = "accordion"; url = "resources/accordion.js"; kind = Script }]

Expand All @@ -629,70 +644,80 @@ module SimplePrimitives =
AttributeMap.ofList [
clazz "ui accordion"

if toggle.IsSome then
onEvent "onopen" [] (List.head >> int >> toggle.Value true)
onEvent "onclose" [] (List.head >> int >> toggle.Value false)
match input.Callback with
| Some cb ->
onEvent "onopen" [] (List.head >> int >> cb true)
onEvent "onclose" [] (List.head >> int >> cb false)

| _ -> ()
]

AttributeMap.union attributes basic

let channel =
match input with
| AccordionInput.Multi (set, _) ->
Some (ASet.channel set)

| AccordionInput.Single (index, _) ->
index |> AVal.map (fun i ->
if i < 0 then SetOperation.Rem -1 // Handle in JS, we don't know the actual index here
else SetOperation.Add i
)
|> AVal.channel
|> Some

| _ -> None

let boot =
let exclusive =
match active with
| Choice2Of2 _ -> "true"
| _ -> "false"
let exclusive = if input.IsExclusive then "true" else "false"
let channel = if channel.IsSome then "channelActive" else "null"

String.concat "" [
"const $self = $('#__ID__');"
"aardvark.accordion($self, " + exclusive + ", channelActive);"
"aardvark.accordion($self, " + exclusive + ", " + channel + ");"
]

let channels =
let channel =
match active with
| Choice1Of2 set -> ASet.channel set
| Choice2Of2 index -> AVal.channel index

[ "channelActive", channel ]
match channel with
| Some ch -> [ "channelActive", ch ]
| _ -> []

require dependencies (
onBoot' channels boot (
Incremental.div attributes <| alist {
Incremental.div attributes <| AList.ofList [
for (title, node) in sections do
div [clazz "title"] [
i [clazz "dropdown icon"] []
text title
Incremental.text title
]
div [clazz "content"] [
node
]
}
]
)
)

/// Simple container dividing content into titled sections, which can be opened and closed.
/// The active set holds the indices of the open sections.
/// The toggle (index, isOpen) message is fired when a section is opened or closed.
let accordion (toggle: int * bool -> 'msg) (active: aset<int>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
sections |> accordionImpl (Some (fun s i -> toggle (i, s))) (Choice1Of2 active) attributes
(attributes: AttributeMap<'msg>) (sections: list<aval<string> * DomNode<'msg>>) =
let cb s i = toggle (i, s)
sections |> accordionImpl (AccordionInput.Multi (active, cb)) attributes

/// Simple container dividing content into titled sections, which can be opened and closed (only one can be open at a time).
/// The active value holds the index of the open section, or -1 if there is no open section.
/// The setActive (index | -1) message is fired when a section is opened or closed.
let accordionExclusive (setActive: int -> 'msg) (active: aval<int>)
(attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
let map o i = if o then i else -1
sections |> accordionImpl (Some (fun s -> map s >> setActive)) (Choice2Of2 active) attributes
(attributes: AttributeMap<'msg>) (sections: list<aval<string> * DomNode<'msg>>) =
let cb s i = (if s then i else -1) |> setActive
sections |> accordionImpl (AccordionInput.Single (active, cb)) attributes

/// Simple container dividing content into titled sections, which can be opened and closed.
/// If exclusive is true, only one section can be open at a time.
let accordionSimple (exclusive: bool) (attributes: AttributeMap<'msg>) (sections: alist<string * DomNode<'msg>>) =
let active =
if exclusive then Choice2Of2 (AVal.constant -1)
else Choice1Of2 ASet.empty

sections |> accordionImpl None active attributes
let accordionSimple (exclusive: bool) (attributes: AttributeMap<'msg>) (sections: list<aval<string> * DomNode<'msg>>) =
sections |> accordionImpl (AccordionInput.Empty exclusive) attributes

[<AutoOpen>]
module ``Primtive Builders`` =
Expand Down Expand Up @@ -966,18 +991,18 @@ module SimplePrimitives =
let inline accordion (toggle: int * bool -> 'msg) (active: aset<int>)
(attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordion toggle active attributes
sections |> List.map (fun (t, n) -> AVal.constant t, n) |> Incremental.accordion toggle active attributes

/// Simple container dividing content into titled sections, which can be opened and closed (only one can be open at a time).
/// The active value holds the index of the open section, or -1 if there is no open section.
/// The setActive (index | -1) message is fired when a section is opened or closed.
let inline accordionExclusive (setActive: int -> 'msg) (active: aval<int>)
(attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordionExclusive setActive active attributes
sections |> List.map (fun (t, n) -> AVal.constant t, n) |> Incremental.accordionExclusive setActive active attributes

/// Simple container dividing content into titled sections, which can be opened and closed.
/// If exclusive is true, only one section can be open at a time.
let inline accordionSimple (exclusive: bool) (attributes: Attribute<'msg> list) (sections: list<string * DomNode<'msg>>) =
let attributes = AttributeMap.ofList attributes
sections |> AList.ofList |> Incremental.accordionSimple exclusive attributes
sections |> List.map (fun (t, n) -> AVal.constant t, n) |> Incremental.accordionSimple exclusive attributes
53 changes: 21 additions & 32 deletions src/Aardvark.UI.Primitives/resources/accordion.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,54 @@
if (!aardvark.accordion) {
/**
* @param {HTMLElement[]} $self
* @param {HTMLElement[]} $content
* @param {HTMLElement} item
* @param {string} event
*/
const onToggle = function ($self, item, event) {
const index = $self.children('.content').index(item);
const onToggle = function ($self, $content, item, event) {
const index = $content.index(item);
aardvark.processEvent($self[0].id, event, index);
};

/**
* @param {HTMLElement[]} $self
* @param {HTMLElement[]} $content
* @param {{cnt: int, value: int}} op
*/
const processMessage = function ($self, op) {
if (op.value < 0 || op.value >= $self.children('.content').length) {
return;
}
const processMessage = function ($self, $content, op) {
var index = op.value;

if (op.cnt > 0) {
$self.accordion('open', op.value);
} else if (op.cnt < 0) {
$self.accordion('close', op.value);
// If we close a section in exclusive mode, we won't get
// the index from Media. Instead we have to figure it out here.
if (op.cnt < 0 && index < 0) {
index = $content.index($content.filter('.active'));
}
};

/**
* @param {HTMLElement[]} $self
* @param {int} index
*/
const processExclusiveMessage = function ($self, index) {
if (index >= 0) {
if (index < $self.children('.content').length) {
if (index >= 0 && index < $content.length) {
if (op.cnt > 0) {
$self.accordion('open', index);
}
} else {
const $content = $self.children('.content');
const active = $content.index($content.filter('.active'));

if (active >= 0) {
$self.accordion('close', active);
} else if (op.cnt < 0) {
$self.accordion('close', index);
}
}
};

/**
* @param {HTMLElement[]} $self
* @param {boolean} exclusive
* @param {{onmessage: function}} channel
* @param {{onmessage: function} | null} channel
*/
aardvark.accordion = function ($self, exclusive, channel) {
const $content = $self.children('.content');

$self.accordion({
exclusive: exclusive,
onOpen: function () { onToggle($self, this, 'onopen'); },
onClose: function () { onToggle($self, this, 'onclose'); }
onOpen: function () { onToggle($self, $content, this, 'onopen'); },
onClose: function () { onToggle($self, $content, this, 'onclose'); }
});

if (exclusive) {
channel.onmessage = (index) => processExclusiveMessage($self, index);
} else {
channel.onmessage = (op) => processMessage($self, op);
if (channel) {
channel.onmessage = (op) => processMessage($self, $content, op);
}
};
}

0 comments on commit b2e0de0

Please sign in to comment.