Skip to content

Instantly share code, notes, and snippets.

@goblinJoel
Last active February 28, 2024 22:13
Show Gist options
  • Save goblinJoel/fa97c3b51f761a7d597e0f2f8ba6c3b4 to your computer and use it in GitHub Desktop.
Save goblinJoel/fa97c3b51f761a7d597e0f2f8ba6c3b4 to your computer and use it in GitHub Desktop.
LiveSelect test page
// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.
import "../css/app.css"
// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
// in "webpack.config.js".
//
// Import deps with the dep name or local files with a relative path, for example:
//
// import {Socket} from "phoenix"
// import socket from "./socket"
//
import "phoenix_html"
import {Socket, LongPoll} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
// liveview-compatible multiselect widget
// import LiveSelect from "live_select"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
// let Hooks = {LiveSelect}
let Hooks = {}
Hooks.LiveSelect = {
attachDomEventHandlers() {
console.log("attachDomEventHandlers()");
this.el.querySelector("input[type=text]").onkeydown = (event) => {
if(event.code === "Enter") {
event.preventDefault()
}
this.pushEventTo(this.el, 'keydown', {key: event.code})
}
},
setInputValue(value) {
console.log("setInputValue()");
this.el.querySelector("input[type=text]").value = value
},
inputEvent(selection, mode) {
console.log("inputEvent()");
const selector = mode === "single" ? "input.hidden" : (selection.length === 0 ? "input[name=live_select_empty_selection]" : "input[type=hidden]")
this.el.querySelector(selector).dispatchEvent(new Event('input', {bubbles: true}))
},
mounted() {
console.log("mounted()");
this.handleEvent("reset", ({id}) => {
console.log("reset")
if(this.el.id === id) {
this.setInputValue(null)
this.inputEvent([], "single")
}
})
this.handleEvent("select", ({id, selection, mode}) => {
console.log("select")
if(this.el.id === id) {
if(mode === "single") {
const [{label}] = selection
this.setInputValue(label)
this.inputEvent(selection, mode)
} else {
this.setInputValue(null)
this.inputEvent(selection, mode)
}
}
})
this.attachDomEventHandlers()
},
updated() {
console.log("updated()");
this.attachDomEventHandlers()
}
}
let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
hooks: Hooks
})
let socket = liveSocket.socket
socket.onError((error, transport, establishedConnections) => {
if(transport === WebSocket && establishedConnections === 0) {
socket.replaceTransport(LongPoll)
socket.connect()
}
})
// Connect if there are any LiveViews on the page
liveSocket.connect()
window.liveSocket = liveSocket
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>My Project</title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= csrf_meta_tag() %>
<script defer type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</head>
<body class="flex flex-col bg-main">
<div>
<%= @inner_content %>
</div>
</body>
</html>
defmodule MyProject.LiveSelectTest do
use Phoenix.LiveView
import LiveSelect
@impl true
def render(assigns) do
~H"""
<.form for={:my_form} :let={f} phx-change="other_change">
<%= live_select f, :city_search %>
</.form>
"""
end
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
@impl true
def handle_info(%LiveSelect.ChangeMsg{} = change_msg, socket) do
cities = [{"a", 1}, {"b", 2}]
# cities could be:
# [ {"city name 1", [lat_1, long_1]}, {"city name 2", [lat_2, long_2]}, ... ]
#
# but it could also be (no coordinates in this case):
# [ "city name 1", "city name 2", ... ]
#
# or:
# [ [label: "city name 1", value: [lat_1, long_1]], [label: "city name 2", value: [lat_2, long_2]], ... ]
#
# or even:
# ["city name 1": [lat_1, long_1], "city name 2": [lat_2, long_2]]
update_options(change_msg, cities)
{:noreply, socket}
end
@impl true
def handle_event(
"change",
%{"my_form" => %{"city_search_text_input" => city_name, "city_search" => city_coords}},
socket
) do
IO.puts("You selected city #{city_name} located at: #{city_coords}")
{:noreply, socket}
end
@impl true
def handle_event(
"other_change",
%{"my_form" => %{"city_search_text_input" => city_name, "city_search" => city_coords}},
socket
) do
IO.puts("OTHER: You selected city #{city_name} located at: #{city_coords}")
{:noreply, socket}
end
end
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>My Project</title>
<link rel="stylesheet" href="/css/app.css"/>
<meta content="Ng8mKEM6cRICN21ELV1qPT5sITEcMS9HcnuZ2l5tcSX2b9RzXUDtsFyu" name="csrf-token">
<script defer type="text/javascript" src="/js/app.js"></script>
</head>
<body class="flex flex-col bg-main">
<div>
<div data-phx-main="true" data-phx-session="redacted" id="phx-FzbeTP8gbdFaTGbB"><form phx-change="other_change">
<div id="my_form_city_search_component" class="relative h-full text-black" name="live-select" phx-hook="LiveSelect" phx-target="1">
<input autocomplete="off" class="rounded-md w-full disabled:bg-gray-100 disabled:placeholder:text-gray-400 disabled:text-gray-400" id="my_form_city_search_text_input" name="my_form[city_search_text_input]" phx-blur="blur" phx-change="change" phx-click="click" phx-debounce="100" phx-focus="focus" phx-keyup="keyup" phx-target="1" type="text">
<!-- TODO: this can become a hidden input when this fix is released: https://github.com/phoenixframework/phoenix_live_view/commit/2d6495a4fd4e3cc9b67ee631102e65b1bc7912f1 -->
<input class="hidden" id="my_form_city_search" name="my_form[city_search]" style="display: none;" type="text">
<ul class="absolute rounded-xl shadow z-50 bg-gray-100 w-full cursor-pointer" style="display: none;" name="live-select-dropdown">
</ul>
</div>
</form></div>
</div>
<iframe hidden height="0" width="0" src="/phoenix/live_reload/frame"></iframe></body>
</html>
# This is just the route I added to the project for this test page.
# the project's LayoutView is the basically-empty default
live_session :liveselect, root_layout: {MyProject.LayoutView, :liveselect_root} do
live "/liveselect", LiveSelectTest
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment