Load texts for input fields only once.
[Sone.git] / src / main / resources / static / javascript / sone.js
1 /* Sone JavaScript functions. */
2
3 function isOnline() {
4         return $("#sone").hasClass("online");
5 }
6
7 function registerInputTextareaSwap(inputSelector, defaultText, inputFieldName, optional, dontUseTextarea) {
8         $(inputSelector).each(function() {
9                 textarea = $(dontUseTextarea ? "<input type=\"text\" name=\"" + inputFieldName + "\">" : "<textarea name=\"" + inputFieldName + "\"></textarea>").blur(function() {
10                         if ($(this).val() == "") {
11                                 $(this).hide();
12                                 inputField = $(this).data("inputField");
13                                 inputField.show().removeAttr("disabled").addClass("default");
14                                 inputField.val(defaultText);
15                         }
16                 }).hide().data("inputField", $(this)).val($(this).val());
17                 $(this).after(textarea);
18                 (function(inputField, textarea) {
19                         inputField.focus(function() {
20                                 $(this).hide().attr("disabled", "disabled");
21                                 textarea.show().focus();
22                         });
23                         if (inputField.val() == "") {
24                                 inputField.addClass("default");
25                                 inputField.val(defaultText);
26                         } else {
27                                 inputField.hide().attr("disabled", "disabled");
28                                 textarea.show();
29                         }
30                         $(inputField.get(0).form).submit(function() {
31                                 if (!optional && (textarea.val() == "")) {
32                                         return false;
33                                 }
34                         });
35                 })($(this), textarea);
36         });
37 }
38
39 /* hide all the “create reply” forms until a link is clicked. */
40 function addCommentLinks() {
41         if (!isOnline()) {
42                 return;
43         }
44         $("#sone .post").each(function() {
45                 postId = $(this).attr("id");
46                 commentElement = (function(postId) {
47                         var commentElement = $("<div><span>Comment</span></div>").addClass("show-reply-form").click(function() {
48                                 replyElement = $("#sone .post#" + postId + " .create-reply");
49                                 replyElement.removeClass("hidden");
50                                 replyElement.removeClass("light");
51                                 (function(replyElement) {
52                                         replyElement.find("input.reply-input").blur(function() {
53                                                 if ($(this).hasClass("default")) {
54                                                         replyElement.addClass("light");
55                                                 }
56                                         }).focus(function() {
57                                                 replyElement.removeClass("light");
58                                         });
59                                 })(replyElement);
60                                 replyElement.find("input.reply-input").focus();
61                         });
62                         return commentElement;
63                 })(postId);
64                 $(this).find(".create-reply").addClass("hidden");
65                 $(this).find(".status-line .time").each(function() {
66                         $(this).after(commentElement.clone(true));
67                 });
68         });
69 }
70
71 /**
72  * Retrieves the translation for the given key and calls the callback function.
73  * The callback function takes a single parameter, the translated string.
74  *
75  * @param key
76  *            The key of the translation string
77  * @param callback
78  *            The callback function
79  */
80 function getTranslation(key, callback) {
81         $.getJSON("ajax/getTranslation.ajax", {"key": key}, function(data, textStatus) {
82                 callback(data.value);
83         });
84 }
85
86 /**
87  * Fires off an AJAX request to retrieve the current status of a Sone.
88  *
89  * @param soneId
90  *            The ID of the Sone
91  */
92 function getSoneStatus(soneId) {
93         $.getJSON("ajax/getSoneStatus.ajax", {"sone": soneId}, function(data, textStatus) {
94                 updateSoneStatus(soneId, data.name, data.status, data.modified, data.lastUpdated);
95                 /* seconds! */
96                 updateInterval = 60;
97                 if (data.modified || (data.status == "downloading") || (data.status == "inserting")) {
98                         updateInterval = 5;
99                 }
100                 setTimeout(function() {
101                         getSoneStatus(soneId);
102                 }, updateInterval * 1000);
103         });
104 }
105
106 /**
107  * Filters the given Sone ID, replacing all “~” characters by an underscore.
108  *
109  * @param soneId
110  *            The Sone ID to filter
111  * @returns The filtered Sone ID
112  */
113 function filterSoneId(soneId) {
114         return soneId.replace(/[^a-zA-Z0-9-]/g, "_");
115 }
116
117 /**
118  * Updates the status of the given Sone.
119  *
120  * @param soneId
121  *            The ID of the Sone to update
122  * @param status
123  *            The status of the Sone (“idle”, “unknown”, “inserting”,
124  *            “downloading”)
125  * @param modified
126  *            Whether the Sone is modified
127  * @param lastUpdated
128  *            The date and time of the last update (formatted for display)
129  */
130 function updateSoneStatus(soneId, name, status, modified, lastUpdated) {
131         $("#sone .sone." + filterSoneId(soneId)).
132                 toggleClass("unknown", status == "unknown").
133                 toggleClass("idle", status == "idle").
134                 toggleClass("inserting", status == "inserting").
135                 toggleClass("downloading", status == "downloading").
136                 toggleClass("modified", modified);
137         $("#sone .sone." + filterSoneId(soneId) + " .last-update span.time").text(lastUpdated);
138         $("#sone .sone." + filterSoneId(soneId) + " .profile-link a").text(name);
139 }
140
141 var watchedSones = {};
142
143 /**
144  * Watches this Sone for updates to its status.
145  *
146  * @param soneId
147  *            The ID of the Sone to watch
148  */
149 function watchSone(soneId) {
150         if (watchedSones[soneId]) {
151                 return;
152         }
153         watchedSones[soneId] = true;
154         (function(soneId) {
155                 setTimeout(function() {
156                         getSoneStatus(soneId);
157                 }, 5000);
158         })(soneId);
159 }
160
161 /**
162  * Enhances a “delete” button so that the confirmation is done on the same page.
163  *
164  * @param buttonId
165  *            The selector of the button
166  * @param text
167  *            The text to show on the button
168  * @param deleteCallback
169  *            The callback that actually deletes something
170  */
171 function enhanceDeleteButton(buttonId, text, deleteCallback) {
172         button = $(buttonId);
173         (function(button) {
174                 newButton = $("<button></button>").addClass("confirm").hide().text(text).click(function() {
175                         $(this).fadeOut("slow");
176                         deleteCallback();
177                         return false;
178                 }).insertAfter(button);
179                 (function(button, newButton) {
180                         button.click(function() {
181                                 button.fadeOut("slow", function() {
182                                         newButton.fadeIn("slow");
183                                         $(document).one("click", function() {
184                                                 if (this != newButton.get(0)) {
185                                                         newButton.fadeOut(function() {
186                                                                 button.fadeIn();
187                                                         });
188                                                 }
189                                         });
190                                 });
191                                 return false;
192                         });
193                 })(button, newButton);
194         })(button);
195 }
196
197 /**
198  * Enhances a post’s “delete” button.
199  *
200  * @param buttonId
201  *            The selector of the button
202  * @param postId
203  *            The ID of the post to delete
204  * @param text
205  *            The text to replace the button with
206  */
207 function enhanceDeletePostButton(buttonId, postId, text) {
208         enhanceDeleteButton(buttonId, text, function() {
209                 $.getJSON("ajax/deletePost.ajax", { "post": postId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
210                         if (data.success) {
211                                 $("#sone .post#" + postId).slideUp();
212                         } else if (data.error == "invalid-post-id") {
213                                 alert("Invalid post ID given!");
214                         } else if (data.error == "auth-required") {
215                                 alert("You need to be logged in.");
216                         } else if (data.error == "not-authorized") {
217                                 alert("You are not allowed to delete this post.");
218                         }
219                 });
220         });
221 }
222
223 /**
224  * Enhances a reply’s “delete” button.
225  *
226  * @param buttonId
227  *            The selector of the button
228  * @param replyId
229  *            The ID of the reply to delete
230  * @param text
231  *            The text to replace the button with
232  */
233 function enhanceDeleteReplyButton(buttonId, replyId, text) {
234         enhanceDeleteButton(buttonId, text, function() {
235                 $.getJSON("ajax/deleteReply.ajax", { "reply": replyId, "formPassword": $("#sone #formPassword").text() }, function(data, textStatus) {
236                         if (data.success) {
237                                 $("#sone .reply#" + replyId).slideUp();
238                         } else if (data.error == "invalid-reply-id") {
239                                 alert("Invalid reply ID given!");
240                         } else if (data.error == "auth-required") {
241                                 alert("You need to be logged in.");
242                         } else if (data.error == "not-authorized") {
243                                 alert("You are not allowed to delete this reply.");
244                         }
245                 });
246         });
247 }
248
249 function getFormPassword() {
250         return $("#sone #formPassword").text();
251 }
252
253 function getSoneElement(element) {
254         return $(element).parents(".sone");
255 }
256
257 /**
258  * Generates a list of Sones by concatening the names of the given sones with a
259  * new line character (“\n”).
260  *
261  * @param sones
262  *            The sones to format
263  * @returns {String} The created string
264  */
265 function generateSoneList(sones) {
266         var soneList = "";
267         $.each(sones, function() {
268                 if (soneList != "") {
269                         soneList += "\n";
270                 }
271                 soneList += this.name;
272         });
273         return soneList;
274 }
275
276 /**
277  * Returns the ID of the Sone that this element belongs to.
278  *
279  * @param element
280  *            The element to locate the matching Sone ID for
281  * @returns The ID of the Sone, or undefined
282  */
283 function getSoneId(element) {
284         return getSoneElement(element).find(".id").text();
285 }
286
287 function getPostElement(element) {
288         return $(element).parents(".post");
289 }
290
291 function getPostId(element) {
292         return getPostElement(element).attr("id");
293 }
294
295 function getReplyElement(element) {
296         return $(element).parents(".reply");
297 }
298
299 function getReplyId(element) {
300         return getReplyElement(element).attr("id");
301 }
302
303 function likePost(postId) {
304         $.getJSON("ajax/like.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function() {
305                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").addClass("hidden");
306                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").removeClass("hidden");
307                 updatePostLikes(postId);
308         });
309 }
310
311 function unlikePost(postId) {
312         $.getJSON("ajax/unlike.ajax", { "type": "post", "post" : postId, "formPassword": getFormPassword() }, function() {
313                 $("#sone .post#" + postId + " > .inner-part > .status-line .unlike").addClass("hidden");
314                 $("#sone .post#" + postId + " > .inner-part > .status-line .like").removeClass("hidden");
315                 updatePostLikes(postId);
316         });
317 }
318
319 function updatePostLikes(postId) {
320         $.getJSON("ajax/getLikes.ajax", { "type": "post", "post": postId }, function(data, textStatus) {
321                 if (data.success) {
322                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes").toggleClass("hidden", data.likes == 0)
323                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes span.like-count").text(data.likes);
324                         $("#sone .post#" + postId + " > .inner-part > .status-line .likes > span").attr("title", generateSoneList(data.sones));
325                 }
326         });
327 }
328
329 function likeReply(replyId) {
330         $.getJSON("ajax/like.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function() {
331                 $("#sone .reply#" + replyId + " .status-line .like").addClass("hidden");
332                 $("#sone .reply#" + replyId + " .status-line .unlike").removeClass("hidden");
333                 updateReplyLikes(replyId);
334         });
335 }
336
337 function unlikeReply(replyId) {
338         $.getJSON("ajax/unlike.ajax", { "type": "reply", "reply" : replyId, "formPassword": getFormPassword() }, function() {
339                 $("#sone .reply#" + replyId + " .status-line .unlike").addClass("hidden");
340                 $("#sone .reply#" + replyId + " .status-line .like").removeClass("hidden");
341                 updateReplyLikes(replyId);
342         });
343 }
344
345 function updateReplyLikes(replyId) {
346         $.getJSON("ajax/getLikes.ajax", { "type": "reply", "reply": replyId }, function(data, textStatus) {
347                 if (data.success) {
348                         $("#sone .reply#" + replyId + " .status-line .likes").toggleClass("hidden", data.likes == 0)
349                         $("#sone .reply#" + replyId + " .status-line .likes span.like-count").text(data.likes);
350                         $("#sone .reply#" + replyId + " .status-line .likes > span").attr("title", generateSoneList(data.sones));
351                 }
352         });
353 }