crockwave

crockwave

Integrate dropdown menus on app list elements

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

First Post!

learning123

learning123

Thank you so much for this detailed answer!! One question:could you clarify a little more on what you mean by assigning a unique value to the element, mapped against the app’s state ? I’m a little confused on what that would look like, and how I could assign a unique_element_id to each of the span options I need for the menu options.

Most Liked

crockwave

crockwave

You assign a unique_element_id to each element that you create with a for statement in your .leex file when you have a common element type, such as a list item. You would normally use a div to contain the unique identifier.

You can see this in action at https://shiptoaster.com, when you host a game, and click on a player or viewer in the left sidebar, where the list is a list of players/viewers, and each can have their own dropdown.

When you create your dropdown span elements in your .leex file, you can use your socket.assigns values to dynamically evaluate one or more of those values so that you can customize how your dropdown span elements are presented. As an example, if you mute a player, you can use that muted state to render a span’ that allows you to unmute that player.

Where Next?

Popular in Guides/Tuts Top

AstonJ
This blog post hit my timeline earlier, and I’ve also been learning about some fantastic Elixir related tips via @pragdave’s new online c...
New
jswny
Hello everyone, I recently redesigned my entire deployment process for Phoenix apps based on Docker. I really like the strategy that I ca...
New
sergio
https://sergiotapia.me/generate-images-with-name-initials-using-elixir-and-imagemagick-374eca4d14ff Hope this saves you guys some time!...
New
fmcgeough
pipe into case? I use that fairly frequently…unless I’m misunderstanding what you’re wanting…could be.. its still very early… str = "Hel...
New
1player
A question I had when first learning contexts and Ecto was how to expand the default context API to support more flexible queries. Usuall...
New
Morzaram
Hey guys I’ve made a guide on how to connect Quill to Phoenix. For the sake of formatting it might be easier to view it on my Notion Doc...
New
hauks96
Hello everyone, I created a deployment tutorial for Phoenix applications with Kubernetes (microk8s) a few months back with the goal of s...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36820 110
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement