(function ($) {
/**
* Terminology:
*
* "Link" means "Everything which is in flag.tpl.php" --and this may contain
* much more than the element. On the other hand, when we speak
* specifically of the element, we say "element" or "the element".
*/
/**
* The main behavior to perform AJAX toggling of links.
*/
Drupal.flagLink = function(context) {
/**
* Helper function. Updates a link's HTML with a new one.
*
* @param element
* The element.
* @return
* The new link.
*/
function updateLink(element, newHtml) {
var $newLink = $(newHtml);
// Initially hide the message so we can fade it in.
$('.flag-message', $newLink).css('display', 'none');
// Reattach the behavior to the new element. This element
// is either whithin the wrapper or it is the outer element itself.
var $nucleus = $newLink.is('a') ? $newLink : $('a.flag', $newLink);
$nucleus.addClass('flag-processed').click(flagClick);
// Find the wrapper of the old link.
var $wrapper = $(element).parents('.flag-wrapper:first');
// Replace the old link with the new one.
$wrapper.after($newLink).remove();
Drupal.attachBehaviors($newLink.get(0));
$('.flag-message', $newLink).fadeIn();
setTimeout(function(){ $('.flag-message.flag-auto-remove', $newLink).fadeOut() }, 3000);
return $newLink.get(0);
}
/**
* A click handler that is attached to all elements.
*/
function flagClick(event) {
// Prevent the default browser click handler
event.preventDefault();
// 'this' won't point to the element when it's inside the ajax closures,
// so we reference it using a variable.
var element = this;
// While waiting for a server response, the wrapper will have a
// 'flag-waiting' class. Themers are thus able to style the link
// differently, e.g., by displaying a throbber.
var $wrapper = $(element).parents('.flag-wrapper');
if ($wrapper.is('.flag-waiting')) {
// Guard against double-clicks.
return false;
}
$wrapper.addClass('flag-waiting');
// Hide any other active messages.
$('span.flag-message:visible').fadeOut();
// Send POST request
$.ajax({
type: 'POST',
url: element.href,
data: { js: true },
dataType: 'json',
success: function (data) {
data.link = $wrapper.get(0);
$.event.trigger('flagGlobalBeforeLinkUpdate', [data]);
if (!data.preventDefault) { // A handler may cancel updating the link.
data.link = updateLink(element, data.newLink);
}
// Find all the link wrappers on the page for this flag, but exclude
// the triggering element because Flag's own javascript updates it.
var $wrappers = $('.flag-wrapper.flag-' + data.flagName.flagNameToCSS() + '-' + data.contentId).not(data.link);
var $newLink = $(data.newLink);
// Hide message, because we want the message to be shown on the triggering element alone.
$('.flag-message', $newLink).hide();
// Finally, update the page.
$wrappers = $newLink.replaceAll($wrappers);
Drupal.attachBehaviors($wrappers.parent());
$.event.trigger('flagGlobalAfterLinkUpdate', [data]);
},
error: function (xmlhttp) {
alert('An HTTP error '+ xmlhttp.status +' occurred.\n'+ element.href);
$wrapper.removeClass('flag-waiting');
}
});
}
$('a.flag-link-toggle:not(.flag-processed)', context).addClass('flag-processed').click(flagClick);
};
/**
* Prevent anonymous flagging unless the user has JavaScript enabled.
*/
Drupal.flagAnonymousLinks = function(context) {
$('a.flag:not(.flag-anonymous-processed)', context).each(function() {
this.href += (this.href.match(/\?/) ? '&' : '?') + 'has_js=1';
$(this).addClass('flag-anonymous-processed');
});
}
String.prototype.flagNameToCSS = function() {
return this.replace(/_/g, '-');
}
/**
* A behavior specifically for anonymous users. Update links to the proper state.
*/
Drupal.flagAnonymousLinkTemplates = function(context) {
// Swap in current links. Cookies are set by PHP's setcookie() upon flagging.
var templates = Drupal.settings.flag.templates;
// Build a list of user-flags.
var userFlags = Drupal.flagCookie('flags');
if (userFlags) {
userFlags = userFlags.split('+');
for (var n in userFlags) {
var flagInfo = userFlags[n].match(/(\w+)_(\d+)/);
var flagName = flagInfo[1];
var contentId = flagInfo[2];
// User flags always default to off and the JavaScript toggles them on.
if (templates[flagName + '_' + contentId]) {
$('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).after(templates[flagName + '_' + contentId]).remove();
}
}
}
// Build a list of global flags.
var globalFlags = document.cookie.match(/flag_global_(\w+)_(\d+)=([01])/g);
if (globalFlags) {
for (var n in globalFlags) {
var flagInfo = globalFlags[n].match(/flag_global_(\w+)_(\d+)=([01])/);
var flagName = flagInfo[1];
var contentId = flagInfo[2];
var flagState = (flagInfo[3] == '1') ? 'flag' : 'unflag';
// Global flags are tricky, they may or may not be flagged in the page
// cache. The template always contains the opposite of the current state.
// So when checking global flag cookies, we need to make sure that we
// don't swap out the link when it's already in the correct state.
if (templates[flagName + '_' + contentId]) {
$('.flag-' + flagName.flagNameToCSS() + '-' + contentId, context).each(function() {
if ($(this).find('.' + flagState + '-action').size()) {
$(this).after(templates[flagName + '_' + contentId]).remove();
}
});
}
}
}
}
/**
* Utility function used to set Flag cookies.
*
* Note this is a direct copy of the jQuery cookie library.
* Written by Klaus Hartl.
*/
Drupal.flagCookie = function(name, value, options) {
if (typeof value != 'undefined') { // name and value given, set cookie
options = options || {};
if (value === null) {
value = '';
options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
}
// NOTE Needed to parenthesize options.path and options.domain
// in the following expressions, otherwise they evaluate to undefined
// in the packed version for some reason...
var path = options.path ? '; path=' + (options.path) : '';
var domain = options.domain ? '; domain=' + (options.domain) : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else { // only name given, get cookie
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};
Drupal.behaviors.flagLink = {};
Drupal.behaviors.flagLink.attach = function(context) {
// For anonymous users with the page cache enabled, swap out links with their
// current state for the user.
if (Drupal.settings.flag && Drupal.settings.flag.templates) {
Drupal.flagAnonymousLinkTemplates(context);
}
// For all anonymous users, require JavaScript for flagging to prevent spiders
// from flagging things inadvertently.
if (Drupal.settings.flag && Drupal.settings.flag.anonymous) {
Drupal.flagAnonymousLinks(context);
}
// On load, bind the click behavior for all links on the page.
Drupal.flagLink(context);
};
})(jQuery);