Use a comma to separate the identities in the tooltips.
[Sone.git] / src / main / resources / static / javascript / sone.js
1 /* Sone JavaScript functions. */
2
3 /* jQuery overrides. */
4 oldGetJson = jQuery.prototype.getJSON;
5 jQuery.prototype.getJSON = function(url, data, successCallback, errorCallback) {
6         if (typeof errorCallback == "undefined") {
7                 return oldGetJson(url, data, successCallback);
8         }
9         if (jQuery.isFunction(data)) {
10                 errorCallback = successCallback;
11                 successCallback = data;
12                 data = null;
13         }
14         return jQuery.ajax({
15                 data: data,
16                 error: errorCallback,
17                 success: successCallback,
18                 url: url
19         });
20 }
21
22 function isOnline() {
23         return $("#sone").hasClass("online");
24 }
25
26 function registerInputTextareaSwap(inputSelector, defaultText, inputFieldName, optional, dontUseTextarea) {
27         $(inputSelector).each(function() {
28                 textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
29                         if ($(this).val() == "") {
30                                 $(this).hide();
31                                 inputField = $(this).data("inputField");
32                                 inputField.show().removeAttr("disabled").addClass("default");
33                                 inputField.val(defaultText);
34                         }
35                 }).hide().data("inputField", $(this)).val($(this).val());
36                 $(this).after(textarea);
37                 (function(inputField, textarea) {
38                         inputField.focus(function() {
39                                 $(this).hide().attr("disabled", "disabled");
40                                 textarea.show().focus();
41                         });
42                         if (inputField.val() == "") {
43                                 inputField.addClass("default");
44                                 inputField.val(defaultText);
45                         } else {
46                                 inputField.hide().attr("disabled", "disabled");
47                                 textarea.show();
48                         }
49                         $(inputField.get(0).form).submit(function() {
50                                 if (!optional && (textarea.val() == "")) {
51                                         return false;
52                                 }
53                         });
54                 })($(this), textarea);
55         });
56 }
57
58 /* hide all the “create reply” forms until a link is clicked. */
59 function addCommentLinks() {
60         if (!isOnline()) {
61                 return;
62         }
63         $("#sone .post").each(function() {
64                 postId = $(this).attr("id");
65                 addCommentLink(postId, $(this));
66         });
67 }
68
69 /**
70  * Adds a “comment” link to all status lines contained in the given element.
71  *
72  * @param postId
73  *            The ID of the post
74  * @param element
75  *            The element to add a “comment” link to
76  */
77 function addCommentLink(postId, element) {
78         commentElement = (function(postId) {
79                 var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
80                         replyElement = $("#sone .post#" + postId + " .create-reply");
81                         replyElement.removeClass("hidden");
82                         replyElement.removeClass("light");
83                         (function(replyElement) {
84                                 replyElement.find("input.reply-input").blur(function() {
85                                         if ($(this).hasClass("default")) {
86                                                 replyElement.addClass("light");
87                                         }
88                                 }).focus(function() {
89                                         replyElement.removeClass("light");
90                                 });
91                         })(replyElement);
92                         replyElement.find("input.reply-input").focus();
93                 });
94                 return commentElement;
95         })(postId);
96         element.find(".create-reply").addClass("hidden");
97         element.find(".status-line .time").each(function() {
98                 $(this).after(commentElement.clone(true));
99         });
100 }
101
102 var translations = {};
103
104 /**
105  * Retrieves the translation for the given key and calls the callback function.
106  * The callback function takes a single parameter, the translated string.
107  *
108  * @param key
109  *            The key of the translation string
110  * @param callback
111  *            The callback function
112  */
113 function getTranslation(key, callback) {
114         if (key in translations) {
115                 callback(translations[key]);
116                 return;
117         }
118         $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
119                 if ((data != null) && data.success) {
120                         translations[key] = data.value;
121                         callback(data.value);
122                 }
123         }, function(xmlHttpRequest, textStatus, error) {
124                 /* ignore error. */
125         });
126 }
127
128 /**
129  * Filters the given Sone ID, replacing all “~” characters by an underscore.
130  *
131  * @param soneId
132  *            The Sone ID to filter
133  * @returns The filtered Sone ID
134  */
135 function filterSoneId(soneId) {
136         return soneId.replace(/[^a-zA-Z0-9-]/g, "_");
137 }
138
139 /**
140  * Updates the status of the given Sone.
141  *
142  * @param soneId
143  *            The ID of the Sone to update
144  * @param status
145  *            The status of the Sone (“idle”, “unknown”, “inserting”,
146  *            “downloading”)
147  * @param modified
148  *            Whether the Sone is modified
149  * @param locked
150  *            Whether the Sone is locked
151  * @param lastUpdated
152  *            The date and time of the last update (formatted for display)
153  */
154 function updateSoneStatus(soneId, name, status, modified, locked, lastUpdated) {
155         $("#sone .sone." + filterSoneId(soneId)).
156                 toggleClass("unknown", status == "unknown").
157                 toggleClass("idle", status == "idle").
158                 toggleClass("inserting", status == "inserting").
159                 toggleClass("downloading", status == "downloading").
160                 toggleClass("modified", modified);
161         $("#sone .sone." + filterSoneId(soneId) + " .lock").toggleClass("hidden", locked);
162         $("#sone .sone." + filterSoneId(soneId) + " .unlock").toggleClass("hidden", !locked);
163         $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
164         $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
165 }
166
167 /**
168  * Enhances a “delete” button so that the confirmation is done on the same page.
169  *
170  * @param buttonId
171  *            The selector of the button
172  * @param text
173  *            The text to show on the button
174  * @param deleteCallback
175  *            The callback that actually deletes something
176  */
177 function enhanceDeleteButton(buttonId, text, deleteCallback) {
178         button = $(buttonId);
179         (function(button) {
180                 newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
181                         $(this).fadeOut("slow");
182                         deleteCallback();
183                         return false;
184                 }).insertAfter(button);
185                 (function(button, newButton) {
186                         button.click(function() {
187                                 button.fadeOut("slow", function() {
188                                         newButton.fadeIn("slow");
189                                         $(document).one("click", function() {
190                                                 if (this != newButton.get(0)) {
191                                                         newButton.fadeOut(function() {
192                                                                 button.fadeIn();
193                                                         });
194                                                 }
195                                         });
196                                 });
197                                 return false;
198                         });
199                 })(button, newButton);
200         })(button);
201 }
202
203 /**
204  * Enhances a post’s “delete” button.
205  *
206  * @param buttonId
207  *            The selector of the button
208  * @param postId
209  *            The ID of the post to delete
210  * @param text
211  *            The text to replace the button with
212  */
213 function enhanceDeletePostButton(buttonId, postId, text) {
214         enhanceDeleteButton(buttonId, text, function() {
215                 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
216                         if (data == null) {
217                                 return;
218                         }
219                         if (data.success) {
220                                 $("#sone .post#" + postId).slideUp();
221                         } else if (data.error == "invalid-post-id") {
222                                 alert("Invalid post ID given!");
223                         } else if (data.error == "auth-required") {
224                                 alert("You need to be logged in.");
225                         } else if (data.error == "not-authorized") {
226                                 alert("You are not allowed to delete this post.");
227                         }
228                 }, function(xmlHttpRequest, textStatus, error) {
229                         /* ignore error. */
230                 });
231         });
232 }
233
234 /**
235  * Enhances a reply’s “delete” button.
236  *
237  * @param buttonId
238  *            The selector of the button
239  * @param replyId
240  *            The ID of the reply to delete
241  * @param text
242  *            The text to replace the button with
243  */
244 function enhanceDeleteReplyButton(buttonId, replyId, text) {
245         enhanceDeleteButton(buttonId, text, function() {
246                 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
247                         if (data == null) {
248                                 return;
249                         }
250                         if (data.success) {
251                                 $("#sone .reply#" + replyId).slideUp();
252                         } else if (data.error == "invalid-reply-id") {
253                                 alert("Invalid reply ID given!");
254                         } else if (data.error == "auth-required") {
255                                 alert("You need to be logged in.");
256                         } else if (data.error == "not-authorized") {
257                                 alert("You are not allowed to delete this reply.");
258                         }
259                 }, function(xmlHttpRequest, textStatus, error) {
260                         /* ignore error. */
261                 });
262         });
263 }
264
265 function getFormPassword() {
266         return $("#sone #formPassword").text();
267 }
268
269 function getSoneElement(element) {
270         return $(element).parents(".sone");
271 }
272
273 /**
274  * Generates a list of Sones by concatening the names of the given sones with a
275  * new line character (“\n”).
276  *
277  * @param sones
278  *            The sones to format
279  * @returns {String} The created string
280  */
281 function generateSoneList(sones) {
282         var soneList = "";
283         $.each(sones, function() {
284                 if (soneList != "") {
285                         soneList += ", ";
286                 }
287                 soneList += this.name;
288         });
289         return soneList;
290 }
291
292 /**
293  * Returns the ID of the Sone that this element belongs to.
294  *
295  * @param element
296  *            The element to locate the matching Sone ID for
297  * @returns The ID of the Sone, or undefined
298  */
299 function getSoneId(element) {
300         return getSoneElement(element).find(".id").text();
301 }
302
303 function getPostElement(element) {
304         return $(element).parents(".post");
305 }
306
307 function getPostId(element) {
308         return getPostElement(element).attr("id");
309 }
310
311 function getReplyElement(element) {
312         return $(element).parents(".reply");
313 }
314
315 function getReplyId(element) {
316         return getReplyElement(element).attr("id");
317 }
318
319 function likePost(postId) {
320         $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
321                 if ((data == null) || !data.success) {
322                         return;
323                 }
324                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").addClass("hidden");
325                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
326                 updatePostLikes(postId);
327         }, function(xmlHttpRequest, textStatus, error) {
328                 /* ignore error. */
329         });
330 }
331
332 function unlikePost(postId) {
333         $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function(data, textStatus) {
334                 if ((data == null) || !data.success) {
335                         return;
336                 }
337                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
338                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
339                 updatePostLikes(postId);
340         }, function(xmlHttpRequest, textStatus, error) {
341                 /* ignore error. */
342         });
343 }
344
345 function updatePostLikes(postId) {
346         $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
347                 if ((data != null) && data.success) {
348                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
349                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
350                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
351                 }
352         }, function(xmlHttpRequest, textStatus, error) {
353                 /* ignore error. */
354         });
355 }
356
357 function likeReply(replyId) {
358         $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
359                 if ((data == null) || !data.success) {
360                         return;
361                 }
362                 $("#sone .reply#" + replyId + " .status-line .like").addClass("hidden");
363                 $("#sone .reply#" + replyId + " .status-line .unlike").removeClass("hidden");
364                 updateReplyLikes(replyId);
365         }, function(xmlHttpRequest, textStatus, error) {
366                 /* ignore error. */
367         });
368 }
369
370 function unlikeReply(replyId) {
371         $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function(data, textStatus) {
372                 if ((data == null) || !data.success) {
373                         return;
374                 }
375                 $("#sone .reply#" + replyId + " .status-line .unlike").addClass("hidden");
376                 $("#sone .reply#" + replyId + " .status-line .like").removeClass("hidden");
377                 updateReplyLikes(replyId);
378         }, function(xmlHttpRequest, textStatus, error) {
379                 /* ignore error. */
380         });
381 }
382
383 function updateReplyLikes(replyId) {
384         $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
385                 if ((data != null) && data.success) {
386                         $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
387                         $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
388                         $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
389                 }
390         }, function(xmlHttpRequest, textStatus, error) {
391                 /* ignore error. */
392         });
393 }
394
395 /**
396  * Posts a reply and calls the given callback when the request finishes.
397  *
398  * @param postId
399  *            The ID of the post the reply refers to
400  * @param text
401  *            The text to post
402  * @param callbackFunction
403  *            The callback function to call when the request finishes (takes 3
404  *            parameters: success, error, replyId)
405  */
406 function postReply(postId, text, callbackFunction) {
407         $.getJSON("ajax/createReply.ajax", { "formPassword" : getFormPassword(), "post" : postId, "text": text }, function(data, textStatus) {
408                 if (data == null) {
409                         /* TODO - show error */
410                         return;
411                 }
412                 if (data.success) {
413                         callbackFunction(true, null, data.reply);
414                 } else {
415                         callbackFunction(false, data.error);
416                 }
417         }, function(xmlHttpRequest, textStatus, error) {
418                 /* ignore error. */
419         });
420 }
421
422 /**
423  * Requests information about the reply with the given ID.
424  *
425  * @param replyId
426  *            The ID of the reply
427  * @param callbackFunction
428  *            A callback function (parameters soneId, soneName, replyTime,
429  *            replyDisplayTime, text, html)
430  */
431 function getReply(replyId, callbackFunction) {
432         $.getJSON("ajax/getReply.ajax", { "reply" : replyId }, function(data, textStatus) {
433                 if ((data != null) && data.success) {
434                         callbackFunction(data.soneId, data.soneName, data.time, data.displayTime, data.text, data.html);
435                 }
436         }, function(xmlHttpRequest, textStatus, error) {
437                 /* ignore error. */
438         });
439 }
440
441 /**
442  * Ajaxifies the given notification by replacing the form with AJAX.
443  *
444  * @param notification
445  *            jQuery object representing the notification.
446  */
447 function ajaxifyNotification(notification) {
448         notification.find("form.dismiss").submit(function() {
449                 return false;
450         });
451         notification.find("form.dismiss button").click(function() {
452                 $.getJSON("ajax/dismissNotification.ajax", { "formPassword" : getFormPassword(), "notification" : notification.attr("id") }, function(data, textStatus) {
453                         /* dismiss in case of error, too. */
454                         notification.slideUp();
455                 }, function(xmlHttpRequest, textStatus, error) {
456                         /* ignore error. */
457                 });
458         });
459         return notification;
460 }
461
462 function getStatus() {
463         $.getJSON("ajax/getStatus.ajax", {}, function(data, textStatus) {
464                 if ((data != null) && data.success) {
465                         /* process Sone information. */
466                         $.each(data.sones, function(index, value) {
467                                 updateSoneStatus(value.id, value.name, value.status, value.modified, value.locked, value.lastUpdated);
468                         });
469                         /* process notifications. */
470                         $.each(data.notifications, function(index, value) {
471                                 oldNotification = $("#sone #notification-area .notification#" + value.id);
472                                 notification = ajaxifyNotification(createNotification(value.id, value.text, value.dismissable)).hide();
473                                 if (oldNotification.length != 0) {
474                                         oldNotification.replaceWith(notification.show());
475                                 } else {
476                                         $("#sone #notification-area").append(notification);
477                                         notification.slideDown();
478                                 }
479                         });
480                         $.each(data.removedNotifications, function(index, value) {
481                                 $("#sone #notification-area .notification#" + value.id).slideUp();
482                         });
483                         /* do it again in 5 seconds. */
484                         setTimeout(getStatus, 5000);
485                 } else {
486                         /* data.success was false, wait 30 seconds. */
487                         setTimeout(getStatus, 30000);
488                 }
489         }, function(xmlHttpRequest, textStatus, error) {
490                 /* something really bad happend, wait a minute. */
491                 setTimeout(getStatus, 60000);
492         })
493 }
494
495 /**
496  * Creates a new notification.
497  *
498  * @param id
499  *            The ID of the notificaiton
500  * @param text
501  *            The text of the notification
502  * @param dismissable
503  *            <code>true</code> if the notification can be dismissed by the
504  *            user
505  */
506 function createNotification(id, text, dismissable) {
507         notification = $("<div></div>").addClass("notification").attr("id", id);
508         if (dismissable) {
509                 dismissForm = $("#sone #notification-area #notification-dismiss-template").clone().removeClass("hidden").removeAttr("id")
510                 dismissForm.find("input[name=notification]").val(id);
511                 notification.append(dismissForm);
512         }
513         notification.append(text);
514         return notification;
515 }
516
517 //
518 // EVERYTHING BELOW HERE IS EXECUTED AFTER LOADING THE PAGE
519 //
520
521 $(document).ready(function() {
522
523         /* this initializes the status update input field. */
524         getTranslation("WebInterface.DefaultText.StatusUpdate", function(text) {
525                 registerInputTextareaSwap("#sone #update-status .status-input", text, "text", false, false);
526         })
527
528         /* this initializes all reply input fields. */
529         getTranslation("WebInterface.DefaultText.Reply", function(text) {
530                 registerInputTextareaSwap("#sone input.reply-input", text, "text", false, false);
531                 addCommentLinks();
532         })
533
534         /* replaces all “post reply!” forms with AJAX. */
535         $("#sone .create-reply button:submit").click(function() {
536                 $(this.form).submit(function() {
537                         return false;
538                 });
539                 inputField = $(this.form).find(":input:enabled").get(0);
540                 postId = getPostId($(inputField));
541                 text = $(inputField).val();
542                 $(inputField).val("");
543                 postReply(postId, text, function(success, error, replyId) {
544                         if (success) {
545                                 getReply(replyId, function(soneId, soneName, replyTime, replyDisplayTime, text, html) {
546                                         newReply = $(html).insertBefore("#sone .post#" + postId + " .create-reply");
547                                         $("#sone .post#" + postId + " .create-reply").addClass("hidden");
548                                         getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(deleteReplyText) {
549                                                 enhanceDeleteReplyButton("#sone .post#" + postId + " .reply#" + replyId + " .delete button", replyId, deleteReplyText);
550                                         });
551                                         newReply.find(".status-line .like").submit(function() {
552                                                 likeReply(getReplyId(this));
553                                                 return false;
554                                         });
555                                         newReply.find(".status-line .unlike").submit(function() {
556                                                 unlikeReply(getReplyId(this));
557                                                 return false;
558                                         });
559                                         addCommentLink(postId, newReply);
560                                 });
561                         } else {
562                                 alert(error);
563                         }
564                 });
565                 return false;
566         });
567
568         /* replace all “delete” buttons with javascript. */
569         getTranslation("WebInterface.Confirmation.DeletePostButton", function(text) {
570                 deletePostText = text;
571                 getTranslation("WebInterface.Confirmation.DeleteReplyButton", function(text) {
572                         deleteReplyText = text;
573                         $("#sone .post").each(function() {
574                                 postId = $(this).attr("id");
575                                 enhanceDeletePostButton("#sone .post#" + postId + " > .inner-part > .status-line .delete button", postId, deletePostText);
576                                 (function(postId) {
577                                         $("#sone .post#" + postId + " .reply").each(function() {
578                                                 replyId = $(this).attr("id");
579                                                 (function(postId, reply, replyId) {
580                                                         reply.find(".delete button").each(function() {
581                                                                 enhanceDeleteReplyButton("#sone .post#" + postId + " .reply#" + replyId + " .delete button", replyId, deleteReplyText);
582                                                         })
583                                                 })(postId, $(this), replyId);
584                                         });
585                                 })(postId);
586                         });
587                 });
588         });
589
590         /* hides all replies but the latest two. */
591         getTranslation("WebInterface.ClickToShow.Replies", function(text) {
592                 $("#sone .post .replies").each(function() {
593                         allReplies = $(this).find(".reply");
594                         if (allReplies.length > 2) {
595                                 newHidden = false;
596                                 for (replyIndex = 0; replyIndex < (allReplies.length - 2); ++replyIndex) {
597                                         $(allReplies[replyIndex]).addClass("hidden");
598                                         newHidden |= $(allReplies[replyIndex]).hasClass("new");
599                                 }
600                                 clickToShowElement = $("<div></div>").addClass("click-to-show");
601                                 if (newHidden) {
602                                         clickToShowElement.addClass("new");
603                                 }
604                                 (function(clickToShowElement, allReplies, text) {
605                                         clickToShowElement.text(text);
606                                         clickToShowElement.click(function() {
607                                                 allReplies.removeClass("hidden");
608                                                 clickToShowElement.addClass("hidden");
609                                         });
610                                 })(clickToShowElement, allReplies, text);
611                                 $(allReplies[0]).before(clickToShowElement);
612                         }
613                 });
614         });
615
616         /*
617          * convert all “follow”, “unfollow”, “lock”, and “unlock” links to something
618          * nicer.
619          */
620         $("#sone .follow").submit(function() {
621                 var followElement = this;
622                 $.getJSON("ajax/followSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
623                         $(followElement).addClass("hidden");
624                         $(followElement).parent().find(".unfollow").removeClass("hidden");
625                 });
626                 return false;
627         });
628         $("#sone .unfollow").submit(function() {
629                 var unfollowElement = this;
630                 $.getJSON("ajax/unfollowSone.ajax", { "sone": getSoneId(this), "formPassword": getFormPassword() }, function() {
631                         $(unfollowElement).addClass("hidden");
632                         $(unfollowElement).parent().find(".follow").removeClass("hidden");
633                 });
634                 return false;
635         });
636         $("#sone .lock").submit(function() {
637                 var lockElement = this;
638                 $.getJSON("ajax/lockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
639                         $(lockElement).addClass("hidden");
640                         $(lockElement).parent().find(".unlock").removeClass("hidden");
641                 });
642                 return false;
643         });
644         $("#sone .unlock").submit(function() {
645                 var unlockElement = this;
646                 $.getJSON("ajax/unlockSone.ajax", { "sone" : getSoneId(this), "formPassword" : getFormPassword() }, function() {
647                         $(unlockElement).addClass("hidden");
648                         $(unlockElement).parent().find(".lock").removeClass("hidden");
649                 });
650                 return false;
651         });
652
653         /* convert all “like” buttons to javascript functions. */
654         $("#sone .post > .inner-part > .status-line .like").submit(function() {
655                 likePost(getPostId(this));
656                 return false;
657         });
658         $("#sone .post > .inner-part > .status-line .unlike").submit(function() {
659                 unlikePost(getPostId(this));
660                 return false;
661         });
662         $("#sone .post .reply .status-line .like").submit(function() {
663                 likeReply(getReplyId(this));
664                 return false;
665         });
666         $("#sone .post .reply .status-line .unlike").submit(function() {
667                 unlikeReply(getReplyId(this));
668                 return false;
669         });
670
671         /* process all existing notifications, ajaxify dismiss buttons. */
672         $("#sone #notification-area .notification").each(function() {
673                 ajaxifyNotification($(this));
674         });
675
676         /* activate status polling. */
677         setTimeout(getStatus, 5000);
678 });