Created
October 27, 2010 15:08
-
-
Save felixge/649208 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gc.widget("gc.cl.widgets.scrollable", {}, function (m, document, $, gc) { | |
$ = jQuery; // Sorry Malte | |
var ANIMATION_SPEED = 800; | |
// A little helper function to sniff common CSS properties that we need | |
// to animate form an event. | |
function getCss($element) { | |
var css = { | |
opacity: $element.css('opacity'), | |
width: $element.outerWidth(), | |
height: $element.outerHeight(), | |
marginLeft: $element.css('marginLeft'), | |
marginTop: $element.css('marginTop'), | |
marginRight: $element.css('marginRight'), | |
marginBottom: $element.css('marginBottom') | |
}; | |
return css; | |
} | |
// A little helper giving us a callback when an image is really loaded | |
function onLoad(imageUrl, fn) { | |
var notify = function() { | |
$loader.remove(); | |
fn(); | |
}, | |
$loader = $('<img />') | |
.attr('src', imageUrl) | |
.appendTo('body') | |
.hide(); | |
// Detect whether the image has been cached. IE7 reports 28px width() on images | |
// that have no yet been loaded, I this is the size of the image-not-found | |
// icon they use. | |
if ($loader.width() > 28) { | |
notify(); | |
return; | |
} | |
$loader.one('load', notify); | |
} | |
this.nodeReady = function(node) { | |
node.find('.js_scrollable').each(function() { | |
var $scrollable = $(this), | |
config = $scrollable.dataset(), | |
$list = $scrollable.find('.js_scrollableitems'), | |
$items = $list.children(), | |
$detail = null, | |
$detailImage = null, | |
detailCss = null, | |
detailShowCss = null, | |
$selectedItem = null, | |
itemWidth = null, | |
selectedItemWidth = null, | |
$spinner = null, | |
state = null, | |
direction = null; | |
// Called once, initiales a few variables and stuff for each scrollable | |
function init() { | |
// Determine itemWidth and selectedItemWidth | |
var $first = $items.first(); | |
itemWidth = $first.outerWidth(); | |
$first.addClass('selected'); | |
selectedItemWidth = $first.outerWidth(); | |
$first.removeClass('selected'); | |
// Determine if there is a detail view (used for looksdetails) | |
$detail = jQuery(config.detailselector); | |
if (!$detail.length) { | |
$detail = null; | |
} else { | |
$detailImage = $detail.find('img'); | |
$detail.addClass('show'); | |
detailShowCss = getCss($detail); | |
$detail.removeClass('show'); | |
detailCss = getCss($detail); | |
if (!$.support.opacity) { | |
// Sniffing opacity doesn't seem to work in IE. Not even when setting | |
// the Alpha Transform filter crap. So we'll have to hard code it here : / | |
detailShowCss.opacity = 1; | |
detailCss.opacity = 0; | |
} | |
} | |
// Load the spinner image if any | |
var spinnerUrl = config.spinnerurl; | |
if (spinnerUrl) { | |
$spinner = $('<img class="spinner" />') | |
.attr('src', spinnerUrl) | |
.appendTo('body') | |
.hide(); | |
} | |
// Set item click listener | |
// We are not using delegate here in order to not make any assumptions | |
// about the type of element $items are made up of | |
$list.bind('click', function(e) { | |
var $item = $(e.target), x = 0; | |
while (true) { | |
if ($list.children().filter($item).length) { | |
break; | |
} | |
$item = $item.parent(); | |
} | |
select($item); | |
}); | |
// Handle mouse scroll events | |
$scrollable.bind('mousewheel', function (e, delta, deltaX, deltaY) { | |
move(deltaX); | |
}); | |
// Handle next/prev arrows | |
$scrollable.delegate('.js_prev, .js_next', 'click', function() { | |
if ($(this).is('.js_next')) { | |
move(1); | |
} else { | |
move(-1); | |
} | |
}); | |
// Cut our list in 2 and insert the second half in the beginning | |
if (config.centerfirst === 'true') { | |
$items | |
.slice($items.length / 2) | |
.insertBefore($first); | |
$items = $list.children(); | |
} | |
setListWidth(); | |
if (config.triplet === 'true') { | |
// Select the 2nd element for triplets like the Adidas slideshow | |
$first = $first.next(); | |
} | |
// Set the initial position and select the first element | |
setListLeft($first); | |
select($first); | |
} | |
function move(offset) { | |
if (!offset) { | |
return; | |
} | |
var $next = (offset < 0) | |
? $selectedItem.prev() | |
: $selectedItem.next(); | |
if (!$next.length) { | |
// This can happen from fanatic mouse scrolling | |
// in this case let's wait for the screen to catch up before trying to move again | |
return; | |
} | |
select($next); | |
} | |
// Makes our list element wide enough to fit all elements, even when one of them is selected | |
function setListWidth() { | |
var width = ($list.children().length - 1) * itemWidth + selectedItemWidth; | |
$list.css('width', width); | |
} | |
// Positions our list so the selected item is centered | |
function setListLeft($centerItem, selected) { | |
$list.css('left', calculateListLeft($centerItem, selected)+'px'); | |
} | |
function calculateListLeft($centerItem, selected) { | |
var screenWidth = $(window).width() | |
centerItemWidth = itemWidth, | |
itemOffset = 0; | |
if (selected) { | |
centerItemWidth = selectedItemWidth; | |
} | |
if ($centerItem) { | |
itemOffset = $centerItem.position().left; | |
} | |
return (screenWidth - centerItemWidth) / 2 - itemOffset; | |
} | |
function select($item) { | |
var $previouslySelectedItem = $selectedItem; | |
function addInfiniteScrollItems() { | |
if (!direction) { | |
// If we didn't move, no need to add items | |
return; | |
} | |
var remainingItems = null, | |
fill = null, | |
neededItems = Math.ceil($(window).width() / itemWidth / 2); | |
// Figure out how many items are remaining in the list depending on the | |
// direction we are currently moving in. `insert` holds the method we | |
// would use if we needed to add more elements in that direction | |
if (direction == 'right') { | |
remainingItems = $selectedItem.nextAll().length; | |
insert = 'appendTo'; | |
} else if (direction == 'left') { | |
remainingItems = $selectedItem.prevAll().length; | |
insert = 'prependTo'; | |
} | |
if (remainingItems < neededItems) { | |
// If we do not have enough items to fill the screen, add a complete batch of new | |
// ones to the list | |
$items | |
.clone() | |
[insert]($list); | |
// After that we need to reset the list width and position | |
setListWidth(); | |
setListLeft($previouslySelectedItem); | |
} | |
} | |
function animatePrevioslySelected() { | |
if (state == 'animatePrevioslySelected') { | |
// If we are already fading the old image out, we just need to set | |
// the new $selectedItem target. | |
$selectedItem = $item; | |
return; | |
} | |
if (state == 'scrollToSelected' || state == 'waitForDetailImage') { | |
// If we are already scrolling to a selected image, we just need to | |
// set the new $selectedItem target. scrollToSelected takes care of | |
// stopping the current animation. | |
$selectedItem = $item; | |
scrollToSelected(); | |
return; | |
} | |
if (state == 'animateSelected') { | |
// If we are in the process of fading a new image in, we are simply | |
// making that image the previouslySelectedItem, so the animation | |
// is stopped and we reversed in this function. | |
$previouslySelectedItem = $selectedItem; | |
} | |
state = 'animatePrevioslySelected'; | |
$previouslySelectedItem.removeClass('selected'); | |
if (!$detail) { | |
// Not much todo here for regular scrollables | |
$selectedItem = $item; | |
scrollToSelected(); | |
return; | |
} | |
// Here we are resizing the selected image container back to be small | |
$previouslySelectedItem | |
.stop() | |
.animate({width: itemWidth}, ANIMATION_SPEED); | |
// Fade the small image back in | |
$previouslySelectedItem | |
.find('img') | |
.stop() | |
.animate({ | |
opacity: 1, | |
marginLeft: 0 | |
}, ANIMATION_SPEED); | |
// Animate the list so we stay centered during the container getting smaller | |
$list | |
.stop() | |
.animate({left: calculateListLeft($previouslySelectedItem)}, ANIMATION_SPEED); | |
// Fade out the big detail image | |
$detail | |
.stop() | |
.animate(detailCss, ANIMATION_SPEED, scrollToSelected) | |
.removeClass('show'); | |
$selectedItem = $item; | |
} | |
function scrollToSelected() { | |
state = 'scrollToSelected'; | |
addInfiniteScrollItems(); | |
if ($detail) { | |
// Remove opacity style from the previusly selected small image | |
// Otherwise potential :hover selectors can't overwrite it | |
$previouslySelectedItem | |
.find('img') | |
.css('opacity', ''); | |
} | |
$list | |
.stop() | |
.animate({left: calculateListLeft($selectedItem)}, ANIMATION_SPEED, function() { | |
if (!$detail) { | |
// no detail image? we are done : ) | |
return; | |
} | |
waitForDetailImage(); | |
}); | |
if ($spinner) { | |
$spinner.hide(); | |
} | |
} | |
function waitForDetailImage() { | |
state = 'waitForDetailImage'; | |
if (!$detail) { | |
// Nothing to do here for regular scrollables | |
animateSelected(); | |
return; | |
} | |
// Set the detail image | |
var detailUrl = $selectedItem.dataset('detailurl') || $img.attr('src'); | |
if ($.support.opacity) { | |
$detailImage.attr('src', detailUrl); | |
} else { | |
// This is needed for IE7, otherwise there is a black border around the transparent image | |
// See discussion here: http://www.optimalworks.net/blog/2008/web-development/ie-png-filter-problems | |
$detailImage.css('filter', 'filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+detailUrl+'", sizingMethod="scale")'); | |
} | |
if ($spinner) { | |
$selectedItem.append($spinner); | |
$spinner.show(); | |
} | |
var $wasSelectedItem = $selectedItem; | |
onLoad(detailUrl, function() { | |
// The user may select another image while he is waiting for the current one to load | |
// if that has happened, we need to ignore the callback from the new deselected image | |
if ($selectedItem !== $wasSelectedItem) { | |
return; | |
} | |
$spinner.remove(); | |
animateSelected(); | |
}); | |
} | |
function animateSelected() { | |
state = 'animateSelected'; | |
if (!$detail) { | |
// Not much todo, just add the selected class | |
$selectedItem.addClass('selected'); | |
$selectedItem.trigger('selected'); | |
return; | |
} | |
// Animate the list so the selected item will be centered when it is | |
// displayed as being selected. | |
$list.animate({left: calculateListLeft($selectedItem, true)}, ANIMATION_SPEED); | |
// Animate the selected item container | |
$selectedItem | |
.animate({width: selectedItemWidth}, ANIMATION_SPEED) | |
.addClass('selected'); | |
// Animate the selected item's small image to fade out | |
$selectedItem | |
.find('img') | |
.animate({ | |
opacity: 0, | |
marginLeft: (selectedItemWidth - itemWidth) / 2 | |
}, ANIMATION_SPEED); | |
// Animate the detail container in | |
$detail | |
.animate(detailShowCss, ANIMATION_SPEED) | |
.addClass('show'); | |
$selectedItem.trigger('selected'); | |
} | |
direction = null; | |
if ($previouslySelectedItem) { | |
direction = 'left'; | |
if ($item.prevAll().filter($previouslySelectedItem).length) { | |
direction = 'right'; | |
} | |
} | |
if (!$previouslySelectedItem) { | |
$selectedItem = $item; | |
waitForDetailImage(); | |
return; | |
} | |
animatePrevioslySelected(); | |
} | |
init(); | |
}); | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment