Multiuser Clipboard

In this multiuser clipboard, all connected users can set the contents of a shared text field, as might be required when passing snippets of text between computers on a local network or the Internet.

Note: this example focuses on the Reactor API, and therefore shows only the bare minimum code required to create a clipboard user interface.

The multiuser clipboard example demonstrates the following Reactor techniques:

  • Creating a room
  • Joining a room
  • Setting room attributes
  • Responding to room attribute updates
  • Automatic reconnection
  • Displaying a user count
  • Displaying a "Connecting..." message
  • Displaying connection status

The Code

Here's the code for the multiuser clipboard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
package {
  import flash.display.Sprite;
  import flash.events.FocusEvent;
  import flash.events.MouseEvent;
  import flash.system.System;
  import flash.text.TextField;
  import flash.text.TextFieldType;
  import flash.text.TextFormat;
  import flash.text.TextFormatAlign;
  import flash.utils.setTimeout;
 
  import net.user1.reactor.AttributeEvent;
  import net.user1.reactor.ConnectionEvent;
  import net.user1.reactor.ConnectionManagerEvent;
  import net.user1.reactor.Reactor;
  import net.user1.reactor.ReactorEvent;
  import net.user1.reactor.Room;
  import net.user1.reactor.RoomEvent;
  import net.user1.reactor.RoomSettings;
 
  public class UnionPasteBoard extends Sprite {
   
//==============================================================================
// VARIABLES
//==============================================================================
    // Constants
    public static const ROOM_NAME:String = "examples.pasteboard";
    public static const PASTEBOARD_ATTRIBUTE:String = "pasteboard";
   
    // Reactor variables
    protected var reactor:Reactor;
    protected var room:Room;
   
    // UI variables
    protected var pasteBoardLabel:TextField;
    protected var numViewers:TextField;
    protected var pasteBoardButton:Sprite;
    protected var pasteBoard:TextField;
    protected var inputLabel:TextField;
    protected var input:TextField;
    protected var status:TextField;
    protected var updateButton:Sprite;
   
   
//==============================================================================
// CONSTRUCTOR
//==============================================================================
   
    public function UnionPasteBoard () {
      // Create user interface
      createUI();
     
      // Create Reactor
      reactor = new Reactor();
     
      // Automatically reconnect every 10 seconds if the connection is lost
      reactor.getConnectionMonitor().setAutoReconnectFrequency(10000);
     
      // Register for Reactor events
      reactor.addEventListener(ReactorEvent.READY, readyListener);
      reactor.addEventListener(ReactorEvent.CLOSE, closeListener);
     
      // Register for ConnectionManager events
      reactor.getConnectionManager().addEventListener(
                                                  ConnectionEvent.BEGIN_CONNECT,
                                                  beginConnectListener);
     
      // Connect to Union
      reactor.connect("tryunion.com", 80);
    }
   
//==============================================================================
// REACTOR LISTENERS
//==============================================================================
   
    // Triggered when the connection is ready
    protected function readyListener (e:ReactorEvent):void {
      // Define settings for the application room
      var settings:RoomSettings = new RoomSettings();
      settings.removeOnEmpty = false; // Make the room permanent
     
      // Create the application room
      room = reactor.getRoomManager().createRoom(UnionPasteBoard.ROOM_NAME,
                                                 settings);
      // Register room event listeners
      room.addEventListener(RoomEvent.SYNCHRONIZE, roomSynchronizeListener);
      room.addEventListener(AttributeEvent.UPDATE,
                            updateRoomAttributeListener);
      room.addEventListener(RoomEvent.OCCUPANT_COUNT, occupantCountListener);
      room.addEventListener(RoomEvent.REMOVED, roomRemovedListener);
      // Join the room
      room.join();
      // Update the on-screen connection status message
      displayStatus("Connected to Union");
    }
   
    // Triggered when the connection is lost or fails
    protected function closeListener (e:ReactorEvent):void {
      displayStatus("Disconnected");
    }
   
//==============================================================================
// CONNECTION MANAGER EVENT LISTENERS
//==============================================================================
   
    // Triggered when a connection attempt begins
    protected function beginConnectListener (e:ConnectionManagerEvent):void {
      displayStatus("Connecting...");
    }
//==============================================================================
// ROOM EVENT LISTENERS
//==============================================================================
   
    // Triggered when one of the room's attributes changes
    protected function updateRoomAttributeListener (e:AttributeEvent):void {
      if (e.getChangedAttr().name == UnionPasteBoard.PASTEBOARD_ATTRIBUTE) {
        displayPasteBoard(e.getChangedAttr().value);
      }
    }
   
    // Triggered when the client-side state of the room has been updated
    // to match the server-side state of the room.
    protected function roomSynchronizeListener (e:RoomEvent):void {
      var pasteBoardContent:String =
                        room.getAttribute(UnionPasteBoard.PASTEBOARD_ATTRIBUTE);
      if (pasteBoardContent != null) {
        displayPasteBoard(pasteBoardContent);
      }
      displayStatus("Ready");
    }
   
    // Triggered when the number of clients in the room changes
    protected function occupantCountListener (e:RoomEvent):void {
      if (e.getNumClients() < 2) {
        displayNumViewers("(No other viewers)");
      } else {
        displayNumViewers("(" + (e.getNumClients()-1) + " other"
                      + (e.getNumClients() > 2 ? "s" : "") + " now viewing)");
      }
    }
   
    // Triggered when the room is removed
    protected function roomRemovedListener (e:RoomEvent):void {
      // If the room is removed while this client is still connected
      // (say, by an admin), then just disconnect and wait for
      // the autoreconnect. The room will be recreated at the next connection.
      if (reactor.isReady()) {
        reactor.disconnect();
      }
    }
   
//==============================================================================
// UI CONTROL
//==============================================================================
   
    public function displayPasteBoard (message:String):void {
      pasteBoard.text = message;
      stage.focus = pasteBoard;
      pasteBoard.setSelection(0, pasteBoard.length);
    }
   
    public function displayStatus (message:String):void {
      status.text = message;
    }
   
    public function displayNumViewers (message:String):void {
      numViewers.text = message;
    }
   
//==============================================================================
// UI LISTENERS
//==============================================================================
   
    protected function updateClickListener (e:MouseEvent):void {
      if (reactor.isReady()
          && reactor.self().isInRoom(UnionPasteBoard.ROOM_NAME)
          && input.length > 0) {
        room.setAttribute(UnionPasteBoard.PASTEBOARD_ATTRIBUTE, input.text);
      }
    }
   
    protected function copyClickListener (e:MouseEvent):void {
      // Only copy if there's text in the paste board.
      if (pasteBoard.length > 0) {
        System.setClipboard(pasteBoard.text);
      }
    }
   
    protected function inputFocusListener (e:FocusEvent):void {
      // Run this 50ms later, otherwise Flash won't select the text
      setTimeout(input.setSelection, 50, 0, input.length);
    }
   
    protected function pasteBoardClickListener (e:MouseEvent):void {
      pasteBoard.setSelection(0, pasteBoard.length);
    }
   
//==============================================================================
// UI CREATION
//==============================================================================
   
    protected function createUI ():void {
      pasteBoardLabel  = createOutputField("Paste Board", 10, 10, 100, 20,
                                           0xFFFFFF, false, true);
      numViewers       = createOutputField("", 90, 10, 200, 20,
                                           0xFFFFFF, false, true);
      pasteBoardButton = createButton("Copy to Clipboard", copyClickListener,  
                                      240, 310, 150, 20);
      pasteBoard       = createOutputField("", 10, 35, 380, 270, 0, true, false);
      pasteBoard.addEventListener(MouseEvent.CLICK, pasteBoardClickListener);
      inputLabel       = createOutputField("Enter text here", 10, 345, 150, 20,
                                           0xFFFFFF, false, true);
      input            = createInputField("", 10, 365, 380, 200);
      input.addEventListener(FocusEvent.FOCUS_IN, inputFocusListener);
      status           = createOutputField("", 10, 570, 200, 20,
                                           0xFFFFFF, false, true);
      updateButton     = createButton("Update Paste Board", updateClickListener,  
                                      240, 570, 150, 20);
     
      addChild(pasteBoardLabel);
      addChild(numViewers);
      addChild(pasteBoardButton);
      addChild(pasteBoard);
      addChild(inputLabel);
      addChild(input);
      addChild(status);
      addChild(updateButton);
    }
   
    protected function createInputField (text:String,
                                         x:int,
                                         y:int,
                                         width:int,
                                         height:int):TextField {
      var t:TextField = new TextField();
      t.text = text;
      t.type = TextFieldType.INPUT;
      t.width = width;
      t.height = height;
      t.x = x;
      t.y = y;
      t.background = true;
      t.border = true;
      t.multiline = true;
      return t;
    }
   
    protected function createOutputField (text:String,
                                          x:int,
                                          y:int,
                                          width:int,
                                          height:int,
                                          color:uint,
                                          border:Boolean,
                                          formatAsLabel:Boolean):TextField {
      var t:TextField = new TextField();
      var format:TextFormat;
      t.width = width;
      t.height = height;
      t.x = x;
      t.y = y;
      t.background = border;
      t.border = border;
     
      if (formatAsLabel) {
        format = new TextFormat();
        format.bold = true;
        format.font = "_sans";
        format.color = color;
        t.defaultTextFormat = format;
        t.selectable = false;
      }
      t.text = text;
      return t;
    }
   
    protected function createButton (label:String,
                                     clickListener:Function,
                                     x:int,
                                     y:int,
                                     width:int,
                                     height:int):Sprite {
      var t:TextField = new TextField();
      t.text = label;
      t.width = width;
      t.height = height;
      t.background = true;
      t.border = true;
      t.selectable = false;
      var format:TextFormat = new TextFormat();
      format.bold = true;
      format.align = TextFormatAlign.CENTER;
      format.font = "_sans";
      t.setTextFormat(format);
      var s:Sprite = new Sprite;
      s.x = x;
      s.y = y;
      s.buttonMode = true;
      s.mouseChildren = false;
      s.addEventListener(MouseEvent.CLICK, clickListener);
      s.addChild(t);
      return s;
    }
  }
}