flag.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. (function ($) {
  2. /**
  3. * Terminology:
  4. *
  5. * "Link" means "Everything which is in flag.tpl.php" --and this may contain
  6. * much more than the <A> element. On the other hand, when we speak
  7. * specifically of the <A> element, we say "element" or "the <A> element".
  8. */
  9. /**
  10. * The main behavior to perform AJAX toggling of links.
  11. */
  12. Drupal.flagLink = function(context) {
  13. /**
  14. * Helper function. Updates a link's HTML with a new one.
  15. *
  16. * @param element
  17. * The <A> element.
  18. * @return
  19. * The new link.
  20. */
  21. function updateLink(element, newHtml) {
  22. var $newLink = $(newHtml);
  23. // Initially hide the message so we can fade it in.
  24. $('.flag-message', $newLink).css('display', 'none');
  25. // Reattach the behavior to the new <A> element. This element
  26. // is either whithin the wrapper or it is the outer element itself.
  27. var $nucleus = $newLink.is('a') ? $newLink : $('a.flag', $newLink);
  28. $nucleus.addClass('flag-processed').click(flagClick);
  29. // Find the wrapper of the old link.
  30. var $wrapper = $(element).parents('.flag-wrapper:first');
  31. // Replace the old link with the new one.
  32. $wrapper.after($newLink).remove();
  33. Drupal.attachBehaviors($newLink.get(0));
  34. $('.flag-message', $newLink).fadeIn();
  35. setTimeout(function(){ $('.flag-message.flag-auto-remove', $newLink).fadeOut() }, 3000);
  36. return $newLink.get(0);
  37. }
  38. /**
  39. * A click handler that is attached to all <A class="flag"> elements.
  40. */
  41. function flagClick(event) {
  42. // Prevent the default browser click handler
  43. event.preventDefault();
  44. // 'this' won't point to the element when it's inside the ajax closures,
  45. // so we reference it using a variable.
  46. var element = this;
  47. // While waiting for a server response, the wrapper will have a
  48. // 'flag-waiting' class. Themers are thus able to style the link
  49. // differently, e.g., by displaying a throbber.
  50. var $wrapper = $(element).parents('.flag-wrapper');
  51. if ($wrapper.is('.flag-waiting')) {
  52. // Guard against double-clicks.
  53. return false;
  54. }
  55. $wrapper.addClass('flag-waiting');
  56. // Hide any other active messages.
  57. $('span.flag-message:visible').fadeOut();
  58. // Send POST request
  59. $.ajax({
  60. type: 'POST',
  61. url: element.href,
  62. data: { js: true },
  63. dataType: 'json',
  64. success: function (data) {
  65. data.link = $wrapper.get(0);
  66. $.event.trigger('flagGlobalBeforeLinkUpdate', [data]);
  67. if (!data.preventDefault) { // A handler may cancel updating the link.
  68. data.link = updateLink(element, data.newLink);
  69. }
  70. // Find all the link wrappers on the page for this flag, but exclude
  71. // the triggering element because Flag's own javascript updates it.
  72. var $wrappers = $('.flag-wrapper.flag-' + data.flagName.flagNameToCSS() + '-' + data.contentId).not(data.link);
  73. var $newLink = $(data.newLink);
  74. // Hide message, because we want the message to be shown on the triggering element alone.
  75. $('.flag-message', $newLink).hide();
  76. // Finally, update the page.
  77. $wrappers = $newLink.replaceAll($wrappers);
  78. Drupal.attachBehaviors($wrappers.parent());
  79. $.event.trigger('flagGlobalAfterLinkUpdate', [data]);
  80. },
  81. error: function (xmlhttp) {
  82. alert('An HTTP error '+ xmlhttp.status +' occurred.\n'+ element.href);
  83. $wrapper.removeClass('flag-waiting');
  84. }
  85. });
  86. }
  87. $('a.flag-link-toggle:not(.flag-processed)', context).addClass('flag-processed').click(flagClick);
  88. };
  89. /**
  90. * Prevent anonymous flagging unless the user has JavaScript enabled.
  91. */
  92. Drupal.flagAnonymousLinks = function(context) {
  93. $('a.flag:not(.flag-anonymous-processed)', context).each(function() {
  94. this.href += (this.href.match(/\?/) ? '&' : '?') + 'has_js=1';
  95. $(this).addClass('flag-anonymous-processed');
  96. });
  97. }
  98. String.prototype.flagNameToCSS = function() {
  99. return this.replace(/_/g, '-');
  100. }
  101. /**
  102. * A behavior specifically for anonymous users. Update links to the proper state.
  103. */
  104. Drupal.flagAnonymousLinkTemplates = function(context) {
  105. // Swap in current links. Cookies are set by PHP's setcookie() upon flagging.
  106. var templates = Drupal.settings.flag.templates;
  107. // Build a list of user-flags.
  108. var userFlags = Drupal.flagCookie('flags');
  109. if (userFlags) {
  110. userFlags = userFlags.split('+');
  111. for (var n in userFlags) {
  112. var flagInfo = userFlags[n].match(/(\w+)_(\d+)/);
  113. var flagName = flagInfo[1];
  114. var contentId = flagInfo[2];
  115. // User flags always default to off and the JavaScript toggles them on.
  116. if (templates[flagName + '_' + contentId]) {
  117. $('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).after(templates[flagName + '_' + contentId]).remove();
  118. }
  119. }
  120. }
  121. // Build a list of global flags.
  122. var globalFlags = document.cookie.match(/flag_global_(\w+)_(\d+)=([01])/g);
  123. if (globalFlags) {
  124. for (var n in globalFlags) {
  125. var flagInfo = globalFlags[n].match(/flag_global_(\w+)_(\d+)=([01])/);
  126. var flagName = flagInfo[1];
  127. var contentId = flagInfo[2];
  128. var flagState = (flagInfo[3] == '1') ? 'flag' : 'unflag';
  129. // Global flags are tricky, they may or may not be flagged in the page
  130. // cache. The template always contains the opposite of the current state.
  131. // So when checking global flag cookies, we need to make sure that we
  132. // don't swap out the link when it's already in the correct state.
  133. if (templates[flagName + '_' + contentId]) {
  134. $('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).each(function() {
  135. if ($(this).find('.' + flagState + '-action').size()) {
  136. $(this).after(templates[flagName + '_' + contentId]).remove();
  137. }
  138. });
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Utility function used to set Flag cookies.
  145. *
  146. * Note this is a direct copy of the jQuery cookie library.
  147. * Written by Klaus Hartl.
  148. */
  149. Drupal.flagCookie = function(name, value, options) {
  150. if (typeof value != 'undefined') { // name and value given, set cookie
  151. options = options || {};
  152. if (value === null) {
  153. value = '';
  154. options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
  155. options.expires = -1;
  156. }
  157. var expires = '';
  158. if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
  159. var date;
  160. if (typeof options.expires == 'number') {
  161. date = new Date();
  162. date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
  163. } else {
  164. date = options.expires;
  165. }
  166. expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
  167. }
  168. // NOTE Needed to parenthesize options.path and options.domain
  169. // in the following expressions, otherwise they evaluate to undefined
  170. // in the packed version for some reason...
  171. var path = options.path ? '; path=' + (options.path) : '';
  172. var domain = options.domain ? '; domain=' + (options.domain) : '';
  173. var secure = options.secure ? '; secure' : '';
  174. document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
  175. } else { // only name given, get cookie
  176. var cookieValue = null;
  177. if (document.cookie && document.cookie != '') {
  178. var cookies = document.cookie.split(';');
  179. for (var i = 0; i < cookies.length; i++) {
  180. var cookie = jQuery.trim(cookies[i]);
  181. // Does this cookie string begin with the name we want?
  182. if (cookie.substring(0, name.length + 1) == (name + '=')) {
  183. cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
  184. break;
  185. }
  186. }
  187. }
  188. return cookieValue;
  189. }
  190. };
  191. Drupal.behaviors.flagLink = {};
  192. Drupal.behaviors.flagLink.attach = function(context) {
  193. // For anonymous users with the page cache enabled, swap out links with their
  194. // current state for the user.
  195. if (Drupal.settings.flag && Drupal.settings.flag.templates) {
  196. Drupal.flagAnonymousLinkTemplates(context);
  197. }
  198. // For all anonymous users, require JavaScript for flagging to prevent spiders
  199. // from flagging things inadvertently.
  200. if (Drupal.settings.flag && Drupal.settings.flag.anonymous) {
  201. Drupal.flagAnonymousLinks(context);
  202. }
  203. // On load, bind the click behavior for all links on the page.
  204. Drupal.flagLink(context);
  205. };
  206. })(jQuery);