1/* 2 3Quicksand 1.2.2 4 5Reorder and filter items with a nice shuffling animation. 6 7Copyright (c) 2010 Jacek Galanciak (razorjack.net) and agilope.com 8Big thanks for Piotr Petrus (riddle.pl) for deep code review and wonderful docs & demos. 9 10Dual licensed under the MIT and GPL version 2 licenses. 11http://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt 12http://github.com/jquery/jquery/blob/master/GPL-LICENSE.txt 13 14Project site: http://razorjack.net/quicksand 15Github site: http://github.com/razorjack/quicksand 16 17*/ 18 19(function ($) { 20 $.fn.quicksand = function (collection, customOptions) { 21 var options = { 22 duration: 750, 23 easing: 'swing', 24 attribute: 'data-id', // attribute to recognize same items within source and dest 25 adjustHeight: 'auto', // 'dynamic' animates height during shuffling (slow), 'auto' adjusts it before or after the animation, false leaves height constant 26 useScaling: true, // disable it if you're not using scaling effect or want to improve performance 27 enhancement: function(c) {}, // Visual enhacement (eg. font replacement) function for cloned elements 28 selector: '> *', 29 dx: 0, 30 dy: 0 31 }; 32 $.extend(options, customOptions); 33 34 if ($.browser.msie || (typeof($.fn.scale) == 'undefined')) { 35 // Got IE and want scaling effect? Kiss my ass. 36 options.useScaling = false; 37 } 38 39 var callbackFunction; 40 if (typeof(arguments[1]) == 'function') { 41 var callbackFunction = arguments[1]; 42 } else if (typeof(arguments[2] == 'function')) { 43 var callbackFunction = arguments[2]; 44 } 45 46 47 return this.each(function (i) { 48 var val; 49 var animationQueue = []; // used to store all the animation params before starting the animation; solves initial animation slowdowns 50 var $collection = $(collection).clone(); // destination (target) collection 51 var $sourceParent = $(this); // source, the visible container of source collection 52 var sourceHeight = $(this).css('height'); // used to keep height and document flow during the animation 53 54 var destHeight; 55 var adjustHeightOnCallback = false; 56 57 var offset = $($sourceParent).offset(); // offset of visible container, used in animation calculations 58 var offsets = []; // coordinates of every source collection item 59 60 var $source = $(this).find(options.selector); // source collection items 61 62 // Replace the collection and quit if IE6 63 if ($.browser.msie && $.browser.version.substr(0,1)<7) { 64 $sourceParent.html('').append($collection); 65 return; 66 } 67 68 // Gets called when any animation is finished 69 var postCallbackPerformed = 0; // prevents the function from being called more than one time 70 var postCallback = function () { 71 72 if (!postCallbackPerformed) { 73 postCallbackPerformed = 1; 74 75 // hack: 76 // used to be: $sourceParent.html($dest.html()); // put target HTML into visible source container 77 // but new webkit builds cause flickering when replacing the collections 78 $toDelete = $sourceParent.find('> *'); 79 $sourceParent.prepend($dest.find('> *')); 80 $toDelete.remove(); 81 82 if (adjustHeightOnCallback) { 83 $sourceParent.css('height', destHeight); 84 } 85 options.enhancement($sourceParent); // Perform custom visual enhancements on a newly replaced collection 86 if (typeof callbackFunction == 'function') { 87 callbackFunction.call(this); 88 } 89 } 90 }; 91 92 // Position: relative situations 93 var $correctionParent = $sourceParent.offsetParent(); 94 var correctionOffset = $correctionParent.offset(); 95 if ($correctionParent.css('position') == 'relative') { 96 if ($correctionParent.get(0).nodeName.toLowerCase() == 'body') { 97 98 } else { 99 correctionOffset.top += (parseFloat($correctionParent.css('border-top-width')) || 0); 100 correctionOffset.left +=( parseFloat($correctionParent.css('border-left-width')) || 0); 101 } 102 } else { 103 correctionOffset.top -= (parseFloat($correctionParent.css('border-top-width')) || 0); 104 correctionOffset.left -= (parseFloat($correctionParent.css('border-left-width')) || 0); 105 correctionOffset.top -= (parseFloat($correctionParent.css('margin-top')) || 0); 106 correctionOffset.left -= (parseFloat($correctionParent.css('margin-left')) || 0); 107 } 108 109 // perform custom corrections from options (use when Quicksand fails to detect proper correction) 110 if (isNaN(correctionOffset.left)) { 111 correctionOffset.left = 0; 112 } 113 if (isNaN(correctionOffset.top)) { 114 correctionOffset.top = 0; 115 } 116 117 correctionOffset.left -= options.dx; 118 correctionOffset.top -= options.dy; 119 120 // keeps nodes after source container, holding their position 121 $sourceParent.css('height', $(this).height()); 122 123 // get positions of source collections 124 $source.each(function (i) { 125 offsets[i] = $(this).offset(); 126 }); 127 128 // stops previous animations on source container 129 $(this).stop(); 130 var dx = 0; var dy = 0; 131 $source.each(function (i) { 132 $(this).stop(); // stop animation of collection items 133 var rawObj = $(this).get(0); 134 if (rawObj.style.position == 'absolute') { 135 dx = -options.dx; 136 dy = -options.dy; 137 } else { 138 dx = options.dx; 139 dy = options.dy; 140 } 141 142 rawObj.style.position = 'absolute'; 143 rawObj.style.margin = '0'; 144 145 rawObj.style.top = (offsets[i].top - parseFloat(rawObj.style.marginTop) - correctionOffset.top + dy) + 'px'; 146 rawObj.style.left = (offsets[i].left - parseFloat(rawObj.style.marginLeft) - correctionOffset.left + dx) + 'px'; 147 }); 148 149 // create temporary container with destination collection 150 var $dest = $($sourceParent).clone(); 151 var rawDest = $dest.get(0); 152 rawDest.innerHTML = ''; 153 rawDest.setAttribute('id', ''); 154 rawDest.style.height = 'auto'; 155 rawDest.style.width = $sourceParent.width() + 'px'; 156 $dest.append($collection); 157 // insert node into HTML 158 // Note that the node is under visible source container in the exactly same position 159 // The browser render all the items without showing them (opacity: 0.0) 160 // No offset calculations are needed, the browser just extracts position from underlayered destination items 161 // and sets animation to destination positions. 162 $dest.insertBefore($sourceParent); 163 $dest.css('opacity', 0.0); 164 rawDest.style.zIndex = -1; 165 166 rawDest.style.margin = '0'; 167 rawDest.style.position = 'absolute'; 168 rawDest.style.top = offset.top - correctionOffset.top + 'px'; 169 rawDest.style.left = offset.left - correctionOffset.left + 'px'; 170 171 172 173 174 175 if (options.adjustHeight === 'dynamic') { 176 // If destination container has different height than source container 177 // the height can be animated, adjusting it to destination height 178 $sourceParent.animate({height: $dest.height()}, options.duration, options.easing); 179 } else if (options.adjustHeight === 'auto') { 180 destHeight = $dest.height(); 181 if (parseFloat(sourceHeight) < parseFloat(destHeight)) { 182 // Adjust the height now so that the items don't move out of the container 183 $sourceParent.css('height', destHeight); 184 } else { 185 // Adjust later, on callback 186 adjustHeightOnCallback = true; 187 } 188 } 189 190 // Now it's time to do shuffling animation
191 // First of all, we need to identify same elements within source and destination collections 192 $source.each(function (i) { 193 var destElement = []; 194 if (typeof(options.attribute) == 'function') { 195 196 val = options.attribute($(this)); 197 $collection.each(function() { 198 if (options.attribute(this) == val) { 199 destElement = $(this); 200 return false; 201 } 202 }); 203 } else { 204 destElement = $collection.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']'); 205 } 206 if (destElement.length) { 207 // The item is both in source and destination collections 208 // It it's under different position, let's move it 209 if (!options.useScaling) { 210 animationQueue.push( 211 { 212 element: $(this), 213 animation: 214 {top: destElement.offset().top - correctionOffset.top, 215 left: destElement.offset().left - correctionOffset.left, 216 opacity: 1.0 217 } 218 }); 219 220 } else { 221 animationQueue.push({ 222 element: $(this), 223 animation: {top: destElement.offset().top - correctionOffset.top, 224 left: destElement.offset().left - correctionOffset.left, 225 opacity: 1.0, 226 scale: '1.0' 227 } 228 }); 229 230 } 231 } else { 232 // The item from source collection is not present in destination collections 233 // Let's remove it 234 if (!options.useScaling) { 235 animationQueue.push({element: $(this), 236 animation: {opacity: '0.0'}}); 237 } else { 238 animationQueue.push({element: $(this), animation: {opacity: '0.0', 239 scale: '0.0'}}); 240 } 241 } 242 }); 243 244 $collection.each(function (i) { 245 // Grab all items from target collection not present in visible source collection 246 247 var sourceElement = []; 248 var destElement = []; 249 if (typeof(options.attribute) == 'function') { 250 val = options.attribute($(this)); 251 $source.each(function() { 252 if (options.attribute(this) == val) { 253 sourceElement = $(this); 254 return false; 255 } 256 }); 257 258 $collection.each(function() { 259 if (options.attribute(this) == val) { 260 destElement = $(this); 261 return false; 262 } 263 }); 264 } else { 265 sourceElement = $source.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']'); 266 destElement = $collection.filter('[' + options.attribute + '=' + $(this).attr(options.attribute) + ']'); 267 } 268 269 var animationOptions; 270 if (sourceElement.length === 0) { 271 // No such element in source collection... 272 if (!options.useScaling) { 273 animationOptions = { 274 opacity: '1.0' 275 }; 276 } else { 277 animationOptions = { 278 opacity: '1.0', 279 scale: '1.0' 280 }; 281 } 282 // Let's create it 283 d = destElement.clone(); 284 var rawDestElement = d.get(0); 285 rawDestElement.style.position = 'absolute'; 286 rawDestElement.style.margin = '0'; 287 rawDestElement.style.top = destElement.offset().top - correctionOffset.top + 'px'; 288 rawDestElement.style.left = destElement.offset().left - correctionOffset.left + 'px'; 289 d.css('opacity', 0.0); // IE 290 if (options.useScaling) { 291 d.css('transform', 'scale(0.0)'); 292 } 293 d.appendTo($sourceParent); 294 295 animationQueue.push({element: $(d), 296 animation: animationOptions}); 297 } 298 }); 299 300 $dest.remove(); 301 options.enhancement($sourceParent); // Perform custom visual enhancements during the animation 302 for (i = 0; i < animationQueue.length; i++) { 303 animationQueue[i].element.animate(animationQueue[i].animation, options.duration, options.easing, postCallback); 304 } 305 }); 306 }; 307})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.