/** * @file * Add jCarousel behaviors to the page and provide Views-support. */ (function($) { Drupal.behaviors.jcarousel = {}; Drupal.behaviors.jcarousel.attach = function(context, settings) { settings = settings || Drupal.settings; // If no carousels exist on this part of the page, work no further. if (!settings.jcarousel || !settings.jcarousel.carousels) { return; } $.each(settings.jcarousel.carousels, function(key, options) { var $carousel = $(options.selector + ':not(.jcarousel-processed)', context); // If this carousel has already been processed or doesn't exist, move on. if (!$carousel.length) { return; } // Callbacks need to be converted from a string to an actual function. $.each(options, function(optionKey) { if (optionKey.match(/Callback$/) && typeof options[optionKey] == 'string') { var callbackFunction = window; var callbackParents = options[optionKey].split('.'); $.each(callbackParents, function(objectParent) { callbackFunction = callbackFunction[callbackParents[objectParent]]; }); options[optionKey] = callbackFunction; } }); // Add standard options required for AJAX functionality. if (options.ajax && !options.itemLoadCallback) { options.itemLoadCallback = Drupal.jcarousel.ajaxLoadCallback; } // If auto-scrolling, pause animation when hoving over the carousel. if (options.auto && options.autoPause && !options.initCallback) { options.initCallback = function(carousel, state) { Drupal.jcarousel.autoPauseCallback(carousel, state); }; } // Add navigation to the carousel if enabled. if (!options.setupCallback) { options.setupCallback = function(carousel) { Drupal.jcarousel.setupCarousel(carousel); if (options.navigation) { Drupal.jcarousel.addNavigation(carousel, options.navigation); } }; if (options.navigation && !options.itemVisibleInCallback) { options.itemLastInCallback = { onAfterAnimation: Drupal.jcarousel.updateNavigationActive }; } } if (!options.hasOwnProperty('buttonNextHTML') && !options.hasOwnProperty('buttonPrevHTML')) { options.buttonNextHTML = Drupal.theme('jCarouselButton', 'next'); options.buttonPrevHTML = Drupal.theme('jCarouselButton', 'previous'); } // Initialize the jcarousel. $carousel.addClass('jcarousel-processed').jcarousel(options); }); }; Drupal.jcarousel = {}; Drupal.jcarousel.ajaxLoadCallback = function(jcarousel, state) { // Check if the requested items already exist. if (state == 'init' || jcarousel.has(jcarousel.first, jcarousel.last)) { return; } var $list = jcarousel.list; var $view = $list.parents('.view:first'); var ajaxPath = Drupal.settings.jcarousel.ajaxPath; var target = $view.get(0); // Find this view's settings in the Views AJAX settings. var settings; $.each(Drupal.settings.jcarousel.carousels, function(domID, carouselSettings) { if ($list.is('.' + domID)) { settings = carouselSettings['view_options']; } }); // Copied from ajax_view.js: var viewData = { 'js': 1, 'first': jcarousel.first - 1, 'last': jcarousel.last }; // Construct an object using the settings defaults and then overriding // with data specific to the link. $.extend( viewData, settings ); $.ajax({ url: ajaxPath, type: 'GET', data: viewData, success: function(response) { Drupal.jcarousel.ajaxResponseCallback(jcarousel, target, response); }, error: function(xhr) { Drupal.jcarousel.ajaxErrorCallback(xhr, ajaxPath); }, dataType: 'json' }); }; /** * Init callback for jCarousel. Pauses the carousel when hovering over. */ Drupal.jcarousel.autoPauseCallback = function(carousel, state) { function pauseAuto() { carousel.stopAuto(); } function resumeAuto() { carousel.startAuto(); } carousel.clip.hover(pauseAuto, resumeAuto); carousel.buttonNext.hover(pauseAuto, resumeAuto); carousel.buttonPrev.hover(pauseAuto, resumeAuto); }; /** * Setup callback for jCarousel. Calculates number of pages. */ Drupal.jcarousel.setupCarousel = function(carousel) { // Determine the number of pages this carousel includes. // This only works for a positive starting point. Also, .first is 1-based // while .last is a count, so we need to reset the .first number to be // 0-based to make the math work. carousel.pageSize = carousel.last - (carousel.first - 1); // jCarousel's Views integration sets "size" in the carousel options. Use that // if available, otherwise count the number of items in the carousel. var itemCount = carousel.options.size ? carousel.options.size : $(carousel.list).children('li').length; carousel.pageCount = Math.ceil(itemCount / carousel.pageSize); carousel.pageNumber = 1; // Disable the previous/next arrows if there is only one page. if (carousel.pageCount == 1) { carousel.buttonNext.addClass('jcarousel-next-disabled').attr('disabled', true); carousel.buttonPrev.addClass('jcarousel-prev-disabled').attr('disabled', true); } // Always remove the hard-coded display: block from the navigation. carousel.buttonNext.css('display', ''); carousel.buttonPrev.css('display', ''); } /** * Setup callback for jCarousel. Adds the navigation to the carousel if enabled. */ Drupal.jcarousel.addNavigation = function(carousel, position) { // Don't add a pager if there's only one page of results. if (carousel.pageCount <= 1) { return; } // Add a class to the wrapper so it can adjust CSS. $(carousel.list).parents('.jcarousel-container:first').addClass('jcarousel-navigation-' + position); var navigation = $(''); for (var i = 1; i <= carousel.pageCount; i++) { var pagerItem = $(Drupal.theme('jCarouselPageLink', i)); var listItem = $('
  • ').attr('jcarousel-page', i).append(pagerItem); navigation.append(listItem); // Make the first page active by default. if (i === 1) { listItem.addClass('active'); } // Scroll to the correct page when a pager is clicked. pagerItem.bind('click', function() { // We scroll to the new page based on item offsets. This works with // circular carousels that do not divide items evenly, making it so that // going back or forward in pages will not skip or repeat any items. var newPageNumber = $(this).parent().attr('jcarousel-page'); var itemOffset = (newPageNumber - carousel.pageNumber) * carousel.pageSize; if (itemOffset) { carousel.scroll(carousel.first + itemOffset); } return false; }); } $(carousel.list).parents('.jcarousel-clip:first')[position](navigation); } /** * itemVisibleInCallback for jCarousel. Update the navigation after page change. */ Drupal.jcarousel.updateNavigationActive = function(carousel, item, idx, state) { // The navigation doesn't even exist yet when this is called on init. var $listItems = $(carousel.list).parents('.jcarousel-container:first').find('.jcarousel-navigation li'); if ($listItems.length == 0) { return; } // jCarousel does some very odd things with circular wraps. Items before the // first item are given negative numbers and items after the last are given // numbers beyond the total number of items. This complicated logic calculates // which page number is active based off this numbering scheme. var pageNumber = Math.ceil(idx / carousel.pageSize); if (pageNumber <= 0 || pageNumber > carousel.pageCount) { pageNumber = pageNumber % carousel.pageCount; pageNumber = pageNumber == 0 ? carousel.pageCount : pageNumber; pageNumber = pageNumber < 0 ? pageNumber + carousel.pageCount : pageNumber; } carousel.pageNumber = pageNumber; var currentPage = $listItems.get(carousel.pageNumber - 1); // Set the current page to be active. $listItems.not(currentPage).removeClass('active'); $(currentPage).addClass('active'); } /** * AJAX callback for all jCarousel-style views. */ Drupal.jcarousel.ajaxResponseCallback = function(jcarousel, target, response) { if (response.debug) { alert(response.debug); } var $view = $(target); var jcarousel = $view.find('ul.jcarousel').data('jcarousel'); // Add items to the jCarousel. $('ul.jcarousel > li', response.display).each(function(i) { var itemNumber = this.className.replace(/.*?jcarousel-item-(\d+).*/, '$1'); jcarousel.add(itemNumber, this.innerHTML); }); // Add Drupal behaviors to the content of the carousel to affect new items. Drupal.attachBehaviors(jcarousel.list.get(0)); // Treat messages the same way that Views typically handles messages. if (response.messages) { // Show any messages (but first remove old ones, if there are any). $view.find('.views-messages').remove().end().prepend(response.messages); } }; /** * Display error messages using the same mechanism as Views module. */ Drupal.jcarousel.ajaxErrorCallback = function (xhr, path) { var error_text = ''; if ((xhr.status == 500 && xhr.responseText) || xhr.status == 200) { error_text = xhr.responseText; // Replace all < and > by < and > error_text = error_text.replace("/&(lt|gt);/g", function (m, p) { return (p == "lt")? "<" : ">"; }); // Now, replace all html tags by empty spaces error_text = error_text.replace(/<("[^"]*"|'[^']*'|[^'">])*>/gi,""); // Fix end lines error_text = error_text.replace(/[\n]+\s+/g,"\n"); } else if (xhr.status == 500) { error_text = xhr.status + ': ' + Drupal.t("Internal server error. Please see server or PHP logs for error information."); } else { error_text = xhr.status + ': ' + xhr.statusText; } alert(Drupal.t("An error occurred at @path.\n\nError Description: @error", {'@path': path, '@error': error_text})); }; Drupal.theme.prototype.jCarouselButton = function(type) { // Use links for buttons for accessibility. return ''; }; Drupal.theme.prototype.jCarouselPageLink = function(pageNumber) { return '' + (pageNumber) + ''; }; })(jQuery);