To integrate dropdown menus in a Phoenix Liveview
app, you can use a combination of js
, Hooks
, CSS
and your .leex
and .ex
code. You can expand on this concept to dynamically adjust your dropdown menu contents, and apply the dropdown
in multiple places in your app.
.leex
Create adiv
with one or more spans
to define your dropdown menu. This example assumes you have multiple items to apply the menu to, you need to assign a unique value to the element, mapped against your app’s state
. Apply as many span
elements as you need for your menu options. Include a phx-hook
reference if you want to listen for click
and hover
events to close the dropdown
You can customize your dropdown span
rendering by examining socket.assigns
values in the .leex
file.
<div class="element_container" id="element_container" phx-hook="DropdownContainer">
<div class = "my_dropdown" phx-click="dropdown" onclick="dropdownOptions('<%= unique_element_id %>')">
<div id="myDropDown<%= unique_element_id %>" class="dropdown-content">
<span phx-click="menu_option_1" phx-value-unique_element_id="<%= unique_element_id %>">Menu Option</span>
</div>
</div>
</div>
Include a script
element in your .leex
to allow toggling of the dropdown menu on click
events.
<script type="text/javascript">
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function dropdownOptions(player) {
var b = "playerDropDown"
var c = b.concat(player)
document.getElementById(c).classList.toggle("show")
}
</script>
CSS
Your CSS should include the following for dropdown rendering. The default behavior is display: none;
until the show
class is detected. The show
class is added/removed/toggled by javascript in the .leex
and .js
files.
.chat_player_dropdown:hover, .chat_player_dropdown:focus {
background-color: #ddd;
}
.my_dropdown {
position: relative;
font-weight: 100;
font-size: 100%;
color: #333333;
cursor: pointer;
}
.dropdown-content {
display: none;
position: absolute;
left: 24px;
background-color: #f1f1f1;
min-width: 55px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
z-index: 998;
}
.dropdown-content span {
color: black;
padding-left: 10px;
padding-right: 10px;
padding-top: 5px;
padding-bottom: 5px;
text-decoration: none;
margin-top: 3px;
line-height: 2.3;
}
.dropdown-content span:hover {
background-color: #ddd;
}
.show {display: block;}
.js
You can add a Hook
to allow the dropdown
to disappear on click
and hover
events on nearby parts of the rendered DOM
, to accommodate users that hover or click elsewhere after activating the dropdown
Hooks.DropdownContainer = {
mounted() {
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.my_dropdown')) {
var dropdowns = document.getElementsByClassName('dropdown-content')
var i
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i]
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show')
}
}
}
}
var item = document.getElementById("element_container");
item.addEventListener("mouseover", e => {
this.pushEvent("restore", {dropdown: "false"})
})
var item = document.getElementById("other_element");
item.addEventListener("mouseover", e => {
this.pushEvent("restore", {dropdown: "false"})
})
}
}
.ex
If you need to control how you render things while your dropdown
is being displayed, you can track it by assigning a dropdown
parameter in
socket.assigns
, with a default value of “false”.
In some of your event handlers, you would assign dropdown
to “false” as a means of allowing normal rendering to commence.
Add a handler as required to detect dropdown
activation
@impl true
def handle_event("dropdown", _params, socket) do
{:noreply, assign(socket, dropdown: "true")}
end