Skip to content

Instantly share code, notes, and snippets.

@mattsan
Last active May 5, 2019 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattsan/e5fbaa6d0303a5be981d7b6a7f3f676f to your computer and use it in GitHub Desktop.
Save mattsan/e5fbaa6d0303a5be981d7b6a7f3f676f to your computer and use it in GitHub Desktop.
Stimulusjs と Channel と Faker を使って 5 秒ごとに挨拶をするサンプル。
defmodule StimulusPhxSampleWeb.Greeter do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, %{})
end
def init(state) do
schedule_greeting()
{:ok, state}
end
def handle_info(:greet, state) do
name = Faker.Name.name()
StimulusPhxSampleWeb.Endpoint.broadcast("room:lobby", "greet", %{body: name})
schedule_greeting()
{:noreply, state}
end
defp schedule_greeting do
Process.send_after(self(), :greet, 5_000)
end
end
import { Application } from 'stimulus'
import HelloController from './controllers/hello_controller'
const application = Application.start()
application.register('hello', HelloController)
import { Controller } from 'stimulus'
import { Socket } from "phoenix"
const socket = new Socket("/socket", {params: {token: window.userToken}})
const channel = socket.channel("room:lobby", {})
class HelloController extends Controller {
static targets = [
'name',
'display'
]
get name() {
return this.nameTarget.value
}
set display(s) {
this.displayTarget.textContent = s
}
connect() {
socket.connect()
channel.join()
.receive("ok", resp => { this.display = `Joined successfully ${JSON.stringify(resp)}` })
.receive("error", resp => { this.display = `Unable to join ${JSON.stringify(resp)}` })
channel.on("greet", payload => {
const name = payload.body
this.display = `Hello, ${name}!`
})
}
greet() {
channel.push("greet", {body: this.name})
}
}
export default HelloController
<div data-controller="hello">
<div class="row">
<input data-target="hello.name" data-action="hello#greet" type="text">
<button data-action="hello#greet">Greet</button>
</div>
<div data-target="hello.display"></div>
</div>
defmodule StimulusPhxSampleWeb.RoomChannel do
use Phoenix.Channel
def join("room:lobby", _message, socket) do
{:ok, socket}
end
def join("room:" <> _private_room_id, _params, _socket) do
{:error, %{reason: "unauthorized"}}
end
def handle_in("greet", %{"body" => body}, socket) do
broadcast!(socket, "greet", %{body: body})
{:noreply, socket}
end
end
diff --git a/assets/.babelrc b/assets/.babelrc
index ce33b24..bb3c6a7 100644
--- a/assets/.babelrc
+++ b/assets/.babelrc
@@ -1,5 +1,4 @@
{
- "presets": [
- "@babel/preset-env"
- ]
+ "presets": ["@babel/preset-env"],
+ "plugins": ["@babel/plugin-proposal-class-properties"]
}
diff --git a/assets/js/app.js b/assets/js/app.js
index 8a5d386..ae88ac4 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -14,4 +14,5 @@ import "phoenix_html"
// Import local files
//
// Local files can be imported directly using relative paths, for example:
-// import socket from "./socket"
+
+import './greeting'
diff --git a/assets/js/controllers/hello_controller.js b/assets/js/controllers/hello_controller.js
new file mode 100644
index 0000000..60483fd
--- /dev/null
+++ b/assets/js/controllers/hello_controller.js
@@ -0,0 +1,39 @@
+import { Controller } from 'stimulus'
+import { Socket } from "phoenix"
+
+const socket = new Socket("/socket", {params: {token: window.userToken}})
+const channel = socket.channel("room:lobby", {})
+
+class HelloController extends Controller {
+ static targets = [
+ 'name',
+ 'display'
+ ]
+
+ get name() {
+ return this.nameTarget.value
+ }
+
+ set display(s) {
+ this.displayTarget.textContent = s
+ }
+
+ connect() {
+ socket.connect()
+
+ channel.join()
+ .receive("ok", resp => { this.display = `Joined successfully ${JSON.stringify(resp)}` })
+ .receive("error", resp => { this.display = `Unable to join ${JSON.stringify(resp)}` })
+
+ channel.on("greet", payload => {
+ const name = payload.body
+ this.display = `Hello, ${name}!`
+ })
+ }
+
+ greet() {
+ channel.push("greet", {body: this.name})
+ }
+}
+
+export default HelloController
diff --git a/assets/js/greeting.js b/assets/js/greeting.js
new file mode 100644
index 0000000..23f2f26
--- /dev/null
+++ b/assets/js/greeting.js
@@ -0,0 +1,5 @@
+import { Application } from 'stimulus'
+import HelloController from './controllers/hello_controller'
+
+const application = Application.start()
+application.register('hello', HelloController)
diff --git a/assets/js/socket.js b/assets/js/socket.js
deleted file mode 100644
index 09929ab..0000000
--- a/assets/js/socket.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// NOTE: The contents of this file will only be executed if
-// you uncomment its entry in "assets/js/app.js".
-
-// To use Phoenix channels, the first step is to import Socket,
-// and connect at the socket path in "lib/web/endpoint.ex".
-//
-// Pass the token on params as below. Or remove it
-// from the params if you are not using authentication.
-import {Socket} from "phoenix"
-
-let socket = new Socket("/socket", {params: {token: window.userToken}})
-
-// When you connect, you'll often need to authenticate the client.
-// For example, imagine you have an authentication plug, `MyAuth`,
-// which authenticates the session and assigns a `:current_user`.
-// If the current user exists you can assign the user's token in
-// the connection for use in the layout.
-//
-// In your "lib/web/router.ex":
-//
-// pipeline :browser do
-// ...
-// plug MyAuth
-// plug :put_user_token
-// end
-//
-// defp put_user_token(conn, _) do
-// if current_user = conn.assigns[:current_user] do
-// token = Phoenix.Token.sign(conn, "user socket", current_user.id)
-// assign(conn, :user_token, token)
-// else
-// conn
-// end
-// end
-//
-// Now you need to pass this token to JavaScript. You can do so
-// inside a script tag in "lib/web/templates/layout/app.html.eex":
-//
-// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
-//
-// You will need to verify the user token in the "connect/3" function
-// in "lib/web/channels/user_socket.ex":
-//
-// def connect(%{"token" => token}, socket, _connect_info) do
-// # max_age: 1209600 is equivalent to two weeks in seconds
-// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
-// {:ok, user_id} ->
-// {:ok, assign(socket, :user, user_id)}
-// {:error, reason} ->
-// :error
-// end
-// end
-//
-// Finally, connect to the socket:
-socket.connect()
-
-// Now that you are connected, you can join channels with a topic:
-let channel = socket.channel("topic:subtopic", {})
-channel.join()
- .receive("ok", resp => { console.log("Joined successfully", resp) })
- .receive("error", resp => { console.log("Unable to join", resp) })
-
-export default socket
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 8bf5005..be25f69 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -76,6 +76,20 @@
"@babel/types": "^7.4.4"
}
},
+ "@babel/helper-create-class-features-plugin": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz",
+ "integrity": "sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-function-name": "^7.1.0",
+ "@babel/helper-member-expression-to-functions": "^7.0.0",
+ "@babel/helper-optimise-call-expression": "^7.0.0",
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/helper-replace-supers": "^7.4.4",
+ "@babel/helper-split-export-declaration": "^7.4.4"
+ }
+ },
"@babel/helper-define-map": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz",
@@ -277,6 +291,16 @@
"@babel/plugin-syntax-async-generators": "^7.2.0"
}
},
+ "@babel/plugin-proposal-class-properties": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz",
+ "integrity": "sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-create-class-features-plugin": "^7.4.4",
+ "@babel/helper-plugin-utils": "^7.0.0"
+ }
+ },
"@babel/plugin-proposal-json-strings": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz",
@@ -785,6 +809,32 @@
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
"dev": true
},
+ "@stimulus/core": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@stimulus/core/-/core-1.1.1.tgz",
+ "integrity": "sha512-PVJv7IpuQx0MVPCBblXc6O2zbCmU8dlxXNH4bC9KK6LsvGaE+PCXXrXQfXUwAsse1/CmRu/iQG7Ov58himjiGg==",
+ "requires": {
+ "@stimulus/mutation-observers": "^1.1.1"
+ }
+ },
+ "@stimulus/multimap": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@stimulus/multimap/-/multimap-1.1.1.tgz",
+ "integrity": "sha512-26R1fI3a8uUj0WlMmta4qcfIQGlagegdP4PTz6lz852q/dXlG6r+uPS/bx+H8GtfyS+OOXVr3SkZ0Zg0iRqRfQ=="
+ },
+ "@stimulus/mutation-observers": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@stimulus/mutation-observers/-/mutation-observers-1.1.1.tgz",
+ "integrity": "sha512-/zCnnw1KJlWO2mrx0yxYaRFZWMGnDMdOgSnI4hxDLxdWVuL2HMROU8FpHWVBLjKY3T9A+lGkcrmPGDHF3pfS9w==",
+ "requires": {
+ "@stimulus/multimap": "^1.1.1"
+ }
+ },
+ "@stimulus/webpack-helpers": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@stimulus/webpack-helpers/-/webpack-helpers-1.1.1.tgz",
+ "integrity": "sha512-XOkqSw53N9072FLHvpLM25PIwy+ndkSSbnTtjKuyzsv8K5yfkFB2rv68jU1pzqYa9FZLcvZWP4yazC0V38dx9A=="
+ },
"acorn": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
@@ -9767,6 +9817,15 @@
}
}
},
+ "stimulus": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stimulus/-/stimulus-1.1.1.tgz",
+ "integrity": "sha512-R0mBqKp48YnRDZOxZ8hiOH4Ilph3Yj78CIFTBkCwyHs4iGCpe7xlEdQ7cjIxb+7qVCSxFKgxO+mAQbsNgt/5XQ==",
+ "requires": {
+ "@stimulus/core": "^1.1.1",
+ "@stimulus/webpack-helpers": "^1.1.1"
+ }
+ },
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
diff --git a/assets/package.json b/assets/package.json
index a4ba0e7..2a4f23d 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -7,10 +7,12 @@
},
"dependencies": {
"phoenix": "file:../deps/phoenix",
- "phoenix_html": "file:../deps/phoenix_html"
+ "phoenix_html": "file:../deps/phoenix_html",
+ "stimulus": "^1.1.1"
},
"devDependencies": {
"@babel/core": "^7.0.0",
+ "@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/preset-env": "^7.0.0",
"babel-loader": "^8.0.0",
"copy-webpack-plugin": "^4.5.0",
diff --git a/lib/stimulus_phx_sample/application.ex b/lib/stimulus_phx_sample/application.ex
index b8c6ade..224356f 100644
--- a/lib/stimulus_phx_sample/application.ex
+++ b/lib/stimulus_phx_sample/application.ex
@@ -9,7 +9,8 @@ defmodule StimulusPhxSample.Application do
# List all child processes to be supervised
children = [
# Start the endpoint when the application starts
- StimulusPhxSampleWeb.Endpoint
+ StimulusPhxSampleWeb.Endpoint,
+ StimulusPhxSampleWeb.Greeter
# Starts a worker by calling: StimulusPhxSample.Worker.start_link(arg)
# {StimulusPhxSample.Worker, arg},
]
diff --git a/lib/stimulus_phx_sample_web/channels/user_socket.ex b/lib/stimulus_phx_sample_web/channels/user_socket.ex
index 1795068..2c2bdb5 100644
--- a/lib/stimulus_phx_sample_web/channels/user_socket.ex
+++ b/lib/stimulus_phx_sample_web/channels/user_socket.ex
@@ -1,33 +1,13 @@
defmodule StimulusPhxSampleWeb.UserSocket do
use Phoenix.Socket
- ## Channels
- # channel "room:*", StimulusPhxSampleWeb.RoomChannel
+ channel "room:*", StimulusPhxSampleWeb.RoomChannel
- # Socket params are passed from the client and can
- # be used to verify and authenticate a user. After
- # verification, you can put default assigns into
- # the socket that will be set for all channels, ie
- #
- # {:ok, assign(socket, :user_id, verified_user_id)}
- #
- # To deny connection, return `:error`.
- #
- # See `Phoenix.Token` documentation for examples in
- # performing token verification on connect.
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
- # Socket id's are topics that allow you to identify all sockets for a given user:
- #
- # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
- #
- # Would allow you to broadcast a "disconnect" event and terminate
- # all active sockets and channels for a given user:
- #
- # StimulusPhxSampleWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
- #
- # Returning `nil` makes this socket anonymous.
- def id(_socket), do: nil
+ def id(socket) do
+ socket.topic
+ end
end
diff --git a/lib/stimulus_phx_sample_web/greeter.ex b/lib/stimulus_phx_sample_web/greeter.ex
new file mode 100644
index 0000000..fdcb500
--- /dev/null
+++ b/lib/stimulus_phx_sample_web/greeter.ex
@@ -0,0 +1,23 @@
+defmodule StimulusPhxSampleWeb.Greeter do
+ use GenServer
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, %{})
+ end
+
+ def init(state) do
+ schedule_greeting()
+ {:ok, state}
+ end
+
+ def handle_info(:greet, state) do
+ name = Faker.Name.name()
+ StimulusPhxSampleWeb.Endpoint.broadcast("room:lobby", "greet", %{body: name})
+ schedule_greeting()
+ {:noreply, state}
+ end
+
+ defp schedule_greeting do
+ Process.send_after(self(), :greet, 5_000)
+ end
+end
diff --git a/lib/stimulus_phx_sample_web/room_channel.ex b/lib/stimulus_phx_sample_web/room_channel.ex
new file mode 100644
index 0000000..dd3cb07
--- /dev/null
+++ b/lib/stimulus_phx_sample_web/room_channel.ex
@@ -0,0 +1,16 @@
+defmodule StimulusPhxSampleWeb.RoomChannel do
+ use Phoenix.Channel
+
+ def join("room:lobby", _message, socket) do
+ {:ok, socket}
+ end
+
+ def join("room:" <> _private_room_id, _params, _socket) do
+ {:error, %{reason: "unauthorized"}}
+ end
+
+ def handle_in("greet", %{"body" => body}, socket) do
+ broadcast!(socket, "greet", %{body: body})
+ {:noreply, socket}
+ end
+end
diff --git a/lib/stimulus_phx_sample_web/templates/layout/app.html.eex b/lib/stimulus_phx_sample_web/templates/layout/app.html.eex
index 5dbaa9b..802908a 100644
--- a/lib/stimulus_phx_sample_web/templates/layout/app.html.eex
+++ b/lib/stimulus_phx_sample_web/templates/layout/app.html.eex
@@ -8,18 +8,6 @@
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
</head>
<body>
- <header>
- <section class="container">
- <nav role="navigation">
- <ul>
- <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
- </ul>
- </nav>
- <a href="http://phoenixframework.org/" class="phx-logo">
- <img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
- </a>
- </section>
- </header>
<main role="main" class="container">
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
diff --git a/lib/stimulus_phx_sample_web/templates/page/index.html.eex b/lib/stimulus_phx_sample_web/templates/page/index.html.eex
index 8cbd9d8..047e5bc 100644
--- a/lib/stimulus_phx_sample_web/templates/page/index.html.eex
+++ b/lib/stimulus_phx_sample_web/templates/page/index.html.eex
@@ -1,35 +1,7 @@
-<section class="phx-hero">
- <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
- <p>A productive web framework that<br/>does not compromise speed or maintainability.</p>
-</section>
-
-<section class="row">
- <article class="column">
- <h2>Resources</h2>
- <ul>
- <li>
- <a href="https://hexdocs.pm/phoenix/overview.html">Guides &amp; Docs</a>
- </li>
- <li>
- <a href="https://github.com/phoenixframework/phoenix">Source</a>
- </li>
- <li>
- <a href="https://github.com/phoenixframework/phoenix/blob/v1.4/CHANGELOG.md">v1.4 Changelog</a>
- </li>
- </ul>
- </article>
- <article class="column">
- <h2>Help</h2>
- <ul>
- <li>
- <a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
- </li>
- <li>
- <a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
- </li>
- <li>
- <a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
- </li>
- </ul>
- </article>
-</section>
+<div data-controller="hello">
+ <div class="row">
+ <input data-target="hello.name" data-action="hello#greet" type="text">
+ <button data-action="hello#greet">Greet</button>
+ </div>
+ <div data-target="hello.display"></div>
+</div>
diff --git a/mix.exs b/mix.exs
index e0eb67a..33cf41d 100644
--- a/mix.exs
+++ b/mix.exs
@@ -38,7 +38,8 @@ defmodule StimulusPhxSample.MixProject do
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
- {:plug_cowboy, "~> 2.0"}
+ {:plug_cowboy, "~> 2.0"},
+ {:faker, "~> 0.12.0"}
]
end
end
diff --git a/mix.lock b/mix.lock
index 88d1449..5c204b6 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,6 +1,7 @@
%{
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
+ "faker": {:hex, :faker, "0.12.0", "796cbac868c86c2df6f273ea4cdf2e271860863820e479e04a374b7ee6c376b6", [:mix], [], "hexpm"},
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment