17fb215baaa11da3be9f64f646a5a35fae86f23b
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / examples / extensions / irc / servlet / index.html
1 <html>
2   <head>
3     <title>ChromiumIRC</title>
4     <link rel="stylesheet" type="text/css" href="styles.css"> 
5     <script src="jstemplate/util.js" type="text/javascript"></script>
6     <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
7     <script src="jstemplate/jstemplate.js" type="text/javascript"></script> 
8     <script src="util.js" type="text/javascript"></script>
9     <script lang="JavaScript" src="irc.js"></script>
10     <script>
11
12 var ircConnections = {};
13
14 // The server & channel configutation data is stored in localStorage.servers.
15 // These are setters and getters for this structure.
16 function servers() {
17   return JSON.parse(localStorage.servers || "[]");
18 }
19 function setServers(servers) {
20   localStorage.servers = JSON.stringify(servers);
21 }
22
23 // Channel list is a sorted list of "server#channel" strings. This maps to
24 // channel slides as represented in the UI.
25 function channelList() {
26   var channelList = [];
27   servers().forEach(function(server) {
28     server.channels = server.channels || [];
29     server.channels.forEach(function(channel) {
30       channelList.push(server.name + channel);
31     });
32   });
33
34   channelList.sort();
35   return channelList;
36 }
37
38 window.onload = function() {
39   // Setup notifications.
40   window.onfocus = function() {
41     windowHasFocus = true;
42     clearNotifications();
43   }
44   window.onblur = function() {
45     windowHasFocus = false;
46   }
47
48   syncChannelList();
49
50   // Setup channel navigation and message entry.
51   function handleBodyKeyDown(event) {
52     switch (event.keyCode) {
53       case 37: // left arrow
54         slideTo(-1);
55         break;
56       case 39: // right arrow
57         slideTo(1);
58         break;
59     }
60   }
61   document.body.addEventListener('keydown', handleBodyKeyDown, false);
62
63   // We don't want left & right arrow inside the text entry to move the channel
64   // slides.
65   $('typingDiv').addEventListener('keydown', function(event) {
66     event.stopPropagation();
67   });
68   $('entryText').addEventListener('keydown', function(event) {
69     if (event.keyCode == 13) { // RETURN key.
70       processEntryMessage();
71     }
72   });
73
74   servers().forEach(addServerConnection);
75 };
76
77 window.onunload = function() {
78   ircConnections.forEach(function(irc) {
79     irc.disconnect();  
80   });
81 }
82
83 function addServerConnection(server) {
84   var ws = new WebSocket("ws://" + location.host + "/ws");
85   var irc = new IRCConnection(server.name, server.port, server.nick,
86                               ws.send.bind(ws), // sendFunc
87                               ws.close.bind(ws)); // closeFunc
88   ws.onopen = irc.onOpened.bind(irc);
89   ws.onclose = irc.onClosed.bind(irc);
90   ws.onmessage = function(message) {
91     irc.onMessage(message.data);  
92   };
93   irc.onConnect = function(message) {
94     server.channels.forEach(function(channel) {
95       ircConnections[server.name].joinChannel(channel);
96     });
97   };
98   irc.onDisconnect = function(message) {
99   };
100   irc.onText = function(channel, nick, message) {
101     checkForNickReference(server, channel, nick, message);
102     addMessage(server.name, channel, nick, new Date(), message);
103   };
104
105   ircConnections[server.name] = irc;
106 }
107
108 function joinChannel(serverName, channelName) {
109   ircConnections[serverName].joinChannel(channelName);
110 }
111
112 function removeChannelListener(channelName) {
113   return function(event) {
114     event.stopPropagation();
115     
116     var servers = servers();
117     servers.forEach(function(server) {
118       if (channelName.indexOf(server.name) == 0) {
119         for (var i = 0; server.channels.length; i++) {
120           if (channelName == server.name + server.channels[i]) {
121             ircConnections[server.name].quitChannel(server.channels[i]);
122             server.channels.splice(i, 1);
123             break;
124           }
125         }
126       }
127     });
128
129     setServers(servers);
130     syncChannelList();
131   };
132 }
133
134 function syncChannelList() {
135   var channels = channelList();
136   var channelSlides = $('channelSlides');
137   var channelSlideProto = $('channelSlideProto');
138
139   var channelIndex = 0;
140   var slideIndex = 0;
141
142   while(channelIndex < channels.length || 
143         channels.length != channelSlides.children.length) {
144     var channel = channels[channelIndex];
145     var slide = channelSlides.children[slideIndex];
146
147     if (slideIndex == channelSlides.children.length ||
148         channel < slideChannel(slide)) {
149       // Add a new slide.
150       var newSlide = channelSlideProto.cloneNode(true);
151       jstProcess(new JsEvalContext({ name: channel }), newSlide);
152       newSlide.setAttribute("id", "channel-" + channel);
153       newSlide.style.display = "";
154       if (slideIndex == channelSlides.children.length) {
155         channelSlides.appendChild(newSlide);
156       } else {
157         channelSlides.insertBefore(newSlide, slide);
158       }
159       newSlide.addEventListener('click', onClickMoveSlide);
160       childNodeWithClass(newSlide, "removeButton")
161           .addEventListener('click', removeChannelListener(channel));
162       
163       slide = newSlide;
164     } else if (!channel || channel > slideChannel(slide)) {
165       // Delete a removed slide.
166       
167       // If the removed slide is the current slide, we have to pick a new
168       // current slide.
169       if (localStorage.currentSlide == slideChannel(slide)) {
170         if (slide.nextSibling) {
171           localStorage.currentSlide = slideChannel(slide.nextSibling);
172         } else if (channels.length == 0) {
173           localStorage.currentSlide = "";
174         } else if (slideIndex < channelSlides.children.length) {
175           localStorage.currentSlide =
176               slideChannel(channelSlides.children[slideIndex - 1]);
177         }
178       }
179       channelSlides.removeChild(slide);  
180     } else {
181       channelIndex++;
182       slideIndex++;
183     }
184
185     slide.setAttribute("slide", "" + slideIndex - 1);
186   }
187
188   slideTo();
189 }
190
191 function processEntryMessage() {
192   var message = $('entryText').value;
193   $('entryText').value = "";
194
195   if (!localStorage.currentSlide) {
196     alert('No current channel');
197     return;
198   }
199   
200   var server;
201   var channel;
202   var nick;
203   servers().forEach(function(s) {
204     if (localStorage.currentSlide.indexOf(s.name) == 0) {
205       server = s.name;
206       nick = s.nick;
207       s.channels.forEach(function(c) {
208         if (localStorage.currentSlide == s.name + c) {
209           channel = c;
210         }
211       });
212     }
213   });
214
215   addMessage(server, channel, nick, new Date(), message);
216   ircConnections[server].sendMessage([channel], message);
217 }
218
219 function addMessage(server, channel, nick, time, body) {
220   messageLine = childNodeWithClass($('channelSlideProto'), "messageLine");
221   var newMessageLine = messageLine.cloneNode(true);
222
223   jstProcess(new JsEvalContext({ 
224     'nick': nick,
225     'time': time,
226     'body': body
227   }), newMessageLine);
228   newMessageLine.style.display = "";
229
230   var messageList =
231       childNodeWithClass($("channel-" + server + channel), "messageList");
232   messageList.appendChild(newMessageLine);
233 }
234
235 function formatTime(time) {
236   return "";
237 }
238
239 /**
240  * Slide Navigation. 
241  */
242  
243 // Returns the server#channel string value for a given |slide| element.
244 function slideChannel(slide) {
245   return childNodeWithClass(slide, "channel").innerText;
246 }
247
248 // Handler for clicking on the visible portions of the previous & next slides.
249 function onClickMoveSlide() {
250   if (localStorage.currentSlide != slideChannel(this)) {
251     localStorage.currentSlide = slideChannel(this);
252     slideTo();
253   }  
254 }
255
256 // Handles navigating between the channel slides. If |slideDelta| is given,
257 // it should specify the number of slides to move left (negative value) or right
258 // positive value. If |slideDelta| is not provided, It ensures that
259 // |localStorage.currentSlide| is navigated to.
260 function slideTo(slideDelta) {
261   var slide;
262   var slideNumber;
263
264   if (localStorage.currentSlide) {
265     slide = document.getElementById("channel-" + localStorage.currentSlide);
266     if (slide) {
267       slideNumber = parseInt(slide.getAttribute("slide"));
268     }
269   }
270   if (isNaN(slideNumber) || !slide) {
271     slideNumber = 0;
272   }
273   if (typeof(slideDelta) == "number") {
274     slideNumber += slideDelta;
275   }
276
277   var slides = document.getElementsByClassName("channelSlide");
278   if (slideNumber < 0 || slideNumber == slides.length - 1) {
279     return;
280   }
281
282   for (var i = 0; i < slides.length; i++) {
283     var slide = slides[i];
284     var slideIndex = parseInt(slide.getAttribute("slide")) - slideNumber;
285     
286     if (slideIndex <= -2) {
287       slide.className = "channelSlide far-left";
288     }
289     if (slideIndex >= 2) {
290       slide.className = "channelSlide far-right";
291     }
292     
293     switch(slideIndex) {
294       case -1:
295         slide.className = "channelSlide left";
296         break;
297       case 0:
298         slide.className = "channelSlide center";
299         localStorage.currentSlide = slideChannel(slide);
300         break;
301       case 1:
302         slide.className = "channelSlide right";
303         break;
304     }
305   }
306
307   clearNotifications();
308 }
309
310 /**
311  * Notifications
312  */
313 var windowHasFocus = false;
314 var notifications = {};
315
316 function clearNotifications() {
317   for (property in notifications) {
318     notifications[property].cancel();
319   }
320
321   notifications = {};
322 }
323
324 function checkForNickReference(server, channel, nick, message) {
325   if (windowHasFocus || !message || message.indexOf(server.nick) < 0) {
326     return;
327   }
328
329   // Notifications will be enabled by the app install. Otherwise, don't notity.
330   if (window.webkitNotifications.checkPermission() != 0) {
331     return;
332   }
333   
334   // Remove a previous notification from the same channel. Show the newer one.
335   if (notifications[server.name + channel]) {
336     notifications[server.name + channel].cancel();
337   }
338
339   var title = "On " + server.name + channel;
340   var icon = "http://www.google.com/favicon.ico";
341   var text = nick + ": " + message;
342   var url = location.protocol + "//" + location.host + "/notification.html";
343   url += "?title=" + encodeURIComponent(title) +
344          "&content=" + encodeURIComponent(text);
345
346   var n = window.webkitNotifications.createHTMLNotification(url);
347   n.ondisplay = function() {};
348   n.onclose = function() {
349     delete notifications[server.name + channel];
350   };
351   n.show();
352
353   notifications[server.name + channel] = n;
354 }
355     </script>
356   </head>
357   <body>
358     <!--  TEMPLATES -->
359     <div id="channelSlideProto" style="display:none" class="channelSlide">
360       <div class="channelControls">
361         <div jscontent="name" class="channel">
362         </div>
363         <div class="removeButton">
364           x
365         </div>
366       </div>
367       <div class="channelSlideContainer">
368         <div class="messageListContainer">
369           <div class="messageList">
370             <div jsselect="messages">
371               <div class="messageLine">
372                 <div jscontent="nick" class="messageSender"></div>:
373                 <div jscontent="body" class="messageBody"></div>
374               </div>
375             </div>
376           </div>
377         </div>
378         <div class="messageListSpacer">.</div>
379       </div>
380     </div>
381     
382     <div id="pageContainer">
383       <div id="headerContainer">
384         <div id="pageControls">
385           <div onclick="window.open('addServer.html');">
386             <div class="addControlLabel">
387               add server
388             </div>
389             <div class="addButton">
390               +
391             </div>
392           </div>
393           <div onclick=" window.open('addChannel.html');">
394             <div class="addControlLabel">
395               add channel
396             </div>
397             <div class="addButton">
398               +
399             </div>
400           </div>
401         </div>
402       </div>
403       <div id="slideContainer">
404         <div id="typingDiv">
405           <input type="text" id="entryText" value="">
406         </div>
407         <div style="" id="channelSlides">
408         </div>
409       </div>
410     </div>
411   </body>
412 </html>