Skip to content
Permalink
Browse files

Merge pull request #34709 from gmcgibbon/action_cable_guides_es6

Use ES6 and Webpacker in ActionCable guide.
  • Loading branch information...
javan committed Jan 5, 2019
2 parents 8651794 + 195da08 commit 2832ca131dd43e15e410af75c1a32664b11595c0
Showing with 160 additions and 112 deletions.
  1. +160 −112 guides/source/action_cable_overview.md
@@ -147,16 +147,13 @@ established using the following JavaScript, which is generated by default by Rai
#### Connect Consumer

```js
// app/assets/javascripts/cable.js
//= require action_cable
//= require_self
//= require_tree ./channels
// app/javascript/channels/consumer.js
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
(function() {
this.App || (this.App = {});
import ActionCable from "actioncable"
App.cable = ActionCable.createConsumer();
}).call(this);
export default ActionCable.createConsumer()
```

This will ready a consumer that'll connect against `/cable` on your server by default.
@@ -167,12 +164,16 @@ you're interested in having.

A consumer becomes a subscriber by creating a subscription to a given channel:

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }
```js
// app/javascript/cable/subscriptions/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" })
// app/javascript/cable/subscriptions/appearance_channel.js
import consumer from "./consumer"
# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create { channel: "AppearanceChannel" }
consumer.subscriptions.create({ channel: "AppearanceChannel" })
```

While this creates the subscription, the functionality needed to respond to
@@ -181,9 +182,12 @@ received data will be described later on.
A consumer can act as a subscriber to a given channel any number of times. For
example, a consumer could subscribe to multiple chat rooms at the same time:

```coffeescript
App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" }
App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" }
```js
// app/javascript/cable/subscriptions/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" })
consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" })
```

## Client-Server Interactions
@@ -256,24 +260,31 @@ When a consumer is subscribed to a channel, they act as a subscriber. This
connection is called a subscription. Incoming messages are then routed to
these channel subscriptions based on an identifier sent by the cable consumer.

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
# Assumes you've already requested the right to send web notifications
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
@appendLine(data)
appendLine: (data) ->
html = @createLine(data)
$("[data-chat-room='Best Room']").append(html)
createLine: (data) ->
"""
<article class="chat-line">
<span class="speaker">#{data["sent_by"]}</span>
<span class="body">#{data["body"]}</span>
</article>
"""
```js
// app/javascript/cable/subscriptions/chat_channel.js
// Assumes you've already requested the right to send web notifications
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
```

### Passing Parameters to Channels
@@ -293,23 +304,30 @@ end
An object passed as the first argument to `subscriptions.create` becomes the
params hash in the cable channel. The keyword `channel` is required:

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
@appendLine(data)
appendLine: (data) ->
html = @createLine(data)
$("[data-chat-room='Best Room']").append(html)
createLine: (data) ->
"""
<article class="chat-line">
<span class="speaker">#{data["sent_by"]}</span>
<span class="body">#{data["body"]}</span>
</article>
"""
```js
// app/javascript/cable/subscriptions/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
this.appendLine(data)
},
appendLine(data) {
const html = this.createLine(data)
const element = document.querySelector("[data-chat-room='Best Room']")
element.insertAdjacentHTML("beforeend", html)
},
createLine(data) {
return `
<article class="chat-line">
<span class="speaker">${data["sent_by"]}</span>
<span class="body">${data["body"]}</span>
</article>
`
}
})
```

```ruby
@@ -340,13 +358,17 @@ class ChatChannel < ApplicationCable::Channel
end
```

```coffeescript
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
# data => { sent_by: "Paul", body: "This is a cool chat app." }
```js
// app/javascript/cable/subscriptions/chat_channel.js
import consumer from "./consumer"
const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, {
received(data) {
// data => { sent_by: "Paul", body: "This is a cool chat app." }
}
}
App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." })
```
The rebroadcast will be received by all connected clients, _including_ the
@@ -396,46 +418,69 @@ appear/disappear API could be backed by Redis, a database, or whatever else.
Create the client-side appearance channel subscription:
```coffeescript
# app/assets/javascripts/cable/subscriptions/appearance.coffee
App.cable.subscriptions.create "AppearanceChannel",
# Called when the subscription is ready for use on the server.
connected: ->
@install()
@appear()
# Called when the WebSocket connection is closed.
disconnected: ->
@uninstall()
# Called when the subscription is rejected by the server.
rejected: ->
@uninstall()
appear: ->
# Calls `AppearanceChannel#appear(data)` on the server.
@perform("appear", appearing_on: $("main").data("appearing-on"))
away: ->
# Calls `AppearanceChannel#away` on the server.
@perform("away")
buttonSelector = "[data-behavior~=appear_away]"
install: ->
$(document).on "turbolinks:load.appearance", =>
@appear()
$(document).on "click.appearance", buttonSelector, =>
@away()
false
$(buttonSelector).show()
uninstall: ->
$(document).off(".appearance")
$(buttonSelector).hide()
```js
// app/javascript/cable/subscriptions/appearance_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("AppearanceChannel", {
// Called once when the subscription is created.
initialized() {
this.update = this.update.bind(this)
},
// Called when the subscription is ready for use on the server.
connected() {
this.install()
this.update()
},
// Called when the WebSocket connection is closed.
disconnected() {
this.uninstall()
},
// Called when the subscription is rejected by the server.
rejected() {
this.uninstall()
},
update() {
this.documentIsActive ? this.appear() : this.away()
},
appear() {
// Calls `AppearanceChannel#appear(data)` on the server.
this.perform("appear", { appearing_on: this.appearingOn })
},
away() {
// Calls `AppearanceChannel#away` on the server.
this.perform("away")
},
install() {
window.addEventListener("focus", this.update)
window.addEventListener("blur", this.update)
document.addEventListener("turbolinks:load", this.update)
document.addEventListener("visibilitychange", this.update)
},
uninstall() {
window.removeEventListener("focus", this.update)
window.removeEventListener("blur", this.update)
document.removeEventListener("turbolinks:load", this.update)
document.removeEventListener("visibilitychange", this.update)
},
get documentIsActive() {
return document.visibilityState == "visible" && document.hasFocus()
},
get appearingOn() {
const element = document.querySelector("[data-appearing-on]")
return element ? element.getAttribute("data-appearing-on") : null
}
})
```
##### Client-Server Interaction
@@ -445,16 +490,16 @@ ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The
**Server** identifies this connection by `current_user`.
2. **Client** subscribes to the appearance channel via
`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`)
`consumer.subscriptions.create({ channel: "AppearanceChannel" })`. (`appearance_channel.js`)
3. **Server** recognizes a new subscription has been initiated for the
appearance channel and runs its `subscribed` callback, calling the `appear`
method on `current_user`. (`appearance_channel.rb`)
4. **Client** recognizes that a subscription has been established and calls
`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`.
`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is
`connected` (`appearance_channel.js`) which in turn calls `install` and `appear`.
`appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a
data hash of `{ appearing_on: this.appearingOn }`. This is
possible because the server-side channel instance automatically exposes all
public methods declared on the class (minus the callbacks), so that these can be
reached as remote procedure calls via a subscription's `perform` method.
@@ -488,13 +533,17 @@ end
Create the client-side web notifications channel subscription:
```coffeescript
# app/assets/javascripts/cable/subscriptions/web_notifications.coffee
# Client-side which assumes you've already requested
# the right to send web notifications.
App.cable.subscriptions.create "WebNotificationsChannel",
received: (data) ->
new Notification data["title"], body: data["body"]
```js
// app/javascript/cable/subscriptions/web_notifications_channel.js
// Client-side which assumes you've already requested
// the right to send web notifications.
import consumer from "./consumer"
consumer.subscriptions.create("WebNotificationsChannel", {
received(data) {
new Notification(data["title"], body: data["body"])
}
})
```
Broadcast content to a web notification channel instance from elsewhere in your
@@ -629,10 +678,9 @@ class Application < Rails::Application
end
```
You can use `App.cable = ActionCable.createConsumer()` to connect to the cable
server if `action_cable_meta_tag` is invoked in the layout. A custom path is
specified as first argument to `createConsumer` (e.g. `App.cable =
ActionCable.createConsumer("/websocket")`).
You can use `ActionCable.createConsumer()` to connect to the cable
server if `action_cable_meta_tag` is invoked in the layout. Otherwise, A path is
specified as first argument to `createConsumer` (e.g. `ActionCable.createConsumer("/websocket")`).
For every instance of your server you create and for every worker your server
spawns, you will also have a new instance of Action Cable, but the use of Redis

0 comments on commit 2832ca1

Please sign in to comment.
You can’t perform that action at this time.