1/*! 2 * sly 1.5.1 - 28th Apr 2015 3 * https://github.com/darsain/sly 4 * 5 * Licensed under the MIT license. 6 * http://opensource.org/licenses/MIT 7 */ 8;(function ($, w, undefined) { 9 'use strict'; 10 11 var pluginName = 'sly'; 12 var className = 'Sly'; 13 var namespace = pluginName; 14 15 // Local WindowAnimationTiming interface 16 var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame; 17 var rAF = w.requestAnimationFrame; 18 19 // Support indicators 20 var transform, gpuAcceleration; 21 22 // Other global values 23 var $doc = $(document); 24 var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace; 25 var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace; 26 var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace; 27 var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace; 28 var clickEvent = 'click.' + namespace; 29 var mouseDownEvent = 'mousedown.' + namespace; 30 var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA']; 31 var tmpArray = []; 32 var time; 33 34 // Math shorthands 35 var abs = Math.abs; 36 var sqrt = Math.sqrt; 37 var pow = Math.pow; 38 var round = Math.round; 39 var max = Math.max; 40 var min = Math.min; 41 42 // Keep track of last fired global wheel event 43 var lastGlobalWheel = 0; 44 /*$doc.on(wheelEvent, function (event) { 45 var sly = event.originalEvent[namespace]; 46 var time = +new Date(); 47 // Update last global wheel time, but only when event didn't originate 48 // in Sly frame, or the origin was less than scrollHijack time ago 49 if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time; 50 });*/ 51 52 /** 53 * Sly. 54 * 55 * @class 56 * 57 * @param {Element} frame DOM element of sly container. 58 * @param {Object} options Object with options. 59 * @param {Object} callbackMap Callbacks map. 60 */ 61 function Sly(frame, options, callbackMap) { 62 // Extend options 63 var o = $.extend({}, Sly.defaults, options); 64 65 // Private variables 66 var self = this; 67 var parallax = isNumber(frame); 68 69 // Frame 70 var $frame = $(frame); 71 var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0); 72 var frameSize = 0; 73 var slideeSize = 0; 74 var pos = { 75 start: 0, 76 center: 0, 77 end: 0, 78 cur: 0, 79 dest: 0 80 }; 81 82 // Scrollbar 83 var $sb = $(o.scrollBar).eq(0); 84 var $handle = $sb.children().eq(0); 85 var sbSize = 0; 86 var handleSize = 0; 87 var hPos = { 88 start: 0, 89 end: 0, 90 cur: 0 91 }; 92 93 // Pagesbar 94 var $pb = $(o.pagesBar); 95 var $pages = 0; 96 var pages = []; 97 98 // Items 99 var $items = 0; 100 var items = []; 101 var rel = { 102 firstItem: 0, 103 lastItem: 0, 104 centerItem: 0, 105 activeItem: null, 106 activePage: 0 107 }; 108 109 // Styles 110 var frameStyles = new StyleRestorer($frame[0]); 111 var slideeStyles = new StyleRestorer($slidee[0]); 112 var sbStyles = new StyleRestorer($sb[0]); 113 var handleStyles = new StyleRestorer($handle[0]); 114 115 // Navigation type booleans 116 var basicNav = o.itemNav === 'basic'; 117 var forceCenteredNav = o.itemNav === 'forceCentered'; 118 var centeredNav = o.itemNav === 'centered' || forceCenteredNav; 119 var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav); 120 121 // Miscellaneous 122 var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame; 123 var $dragSource = o.dragSource ? $(o.dragSource) : $frame; 124 var $forwardButton = $(o.forward); 125 var $backwardButton = $(o.backward); 126 var $prevButton = $(o.prev); 127 var $nextButton = $(o.next); 128 var $prevPageButton = $(o.prevPage); 129 var $nextPageButton = $(o.nextPage); 130 var callbacks = {}; 131 var last = {}; 132 var animation = {}; 133 var move = {}; 134 var dragging = { 135 released: 1 136 }; 137 var scrolling = { 138 last: 0, 139 delta: 0, 140 resetTime: 200 141 }; 142 var renderID = 0; 143 var historyID = 0; 144 var cycleID = 0; 145 var continuousID = 0; 146 var i, l; 147 148 // Normalizing frame 149 if (!parallax) { 150 frame = $frame[0]; 151 } 152 153 // Expose properties 154 self.initialized = 0; 155 self.frame = frame; 156 self.slidee = $slidee[0]; 157 self.pos = pos; 158 self.rel = rel; 159 self.items = items; 160 self.pages = pages; 161 self.isPaused = 0; 162 self.options = o; 163 self.dragging = dragging; 164 165 /** 166 * Loading function. 167 * 168 * Populate arrays, set sizes, bind events, ... 169 * 170 * @param {Boolean} [isInit] Whether load is called from within self.init(). 171 * @return {Void} 172 */ 173 function load(isInit) { 174 // Local variables 175 var lastItemsCount = 0; 176 var lastPagesCount = pages.length; 177 178 // Save old position 179 pos.old = $.extend({}, pos); 180 181 // Reset global variables 182 frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height'](); 183 sbSize = $sb[o.horizontal ? 'width' : 'height']();
184 slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight'](); 185 pages.length = 0; 186 187 // Set position limits & relatives 188 pos.start = 0; 189 pos.end = max(slideeSize - frameSize, 0); 190 191 // Sizes & offsets for item based navigations 192 if (itemNav) { 193 // Save the number of current items 194 lastItemsCount = items.length; 195 196 // Reset itemNav related variables 197 $items = $slidee.children(o.itemSelector); 198 items.length = 0; 199 200 // Needed variables 201 var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop'); 202 var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom'); 203 var borderBox = $($items).css('boxSizing') === 'border-box'; 204 var areFloated = $items.css('float') !== 'none'; 205 var ignoredMargin = 0; 206 var lastItemIndex = $items.length - 1; 207 var lastItem; 208 209 // Reset slideeSize 210 slideeSize = 0; 211 212 // Iterate through items 213 $items.each(function (i, element) { 214 // Item 215 var $item = $(element); 216 var rect = element.getBoundingClientRect(); 217 var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top); 218 var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop'); 219 var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom'); 220 if($item.parents('.frame.props').length){ 221 if($item.find('td').eq(0).length){ 222 itemSize -= $item.find('td').eq(0).outerWidth(); 223 } 224 } 225 var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd; 226 var singleSpaced = !itemMarginStart || !itemMarginEnd; 227 var item = {}; 228 item.el = element; 229 item.size = singleSpaced ? itemSize : itemSizeFull; 230 item.half = item.size / 2; 231 item.start = slideeSize + (singleSpaced ? itemMarginStart : 0); 232 item.center = item.start - round(frameSize / 2 - item.size / 2); 233 item.end = item.start - frameSize + item.size; 234 235 // Account for slidee padding 236 if (!i) { 237 slideeSize += paddingStart; 238 } 239 240 // Increment slidee size for size of the active element 241 slideeSize += itemSizeFull; 242 243 // Try to account for vertical margin collapsing in vertical mode 244 // It's not bulletproof, but should work in 99% of cases 245 if (!o.horizontal && !areFloated) { 246 // Subtract smaller margin, but only when top margin is not 0, and this is not the first element 247 if (itemMarginEnd && itemMarginStart && i > 0) { 248 slideeSize -= min(itemMarginStart, itemMarginEnd); 249 } 250 } 251 252 // Things to be done on last item 253 if (i === lastItemIndex) { 254 item.end += paddingEnd; 255 slideeSize += paddingEnd; 256 ignoredMargin = singleSpaced ? itemMarginEnd : 0; 257 } 258 259 // Add item object to items array 260 items.push(item); 261 lastItem = item; 262 }); 263 264 // Resize SLIDEE to fit all items 265 $slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px'; 266 267 // Adjust internal SLIDEE size for last margin 268 slideeSize -= ignoredMargin; 269 270 // Set limits 271 if (items.length) { 272 pos.start = items[0][forceCenteredNav ? 'center' : 'start']; 273 pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start; 274 } else { 275 pos.start = pos.end = 0; 276 } 277 } 278 279 // Calculate SLIDEE center position 280 pos.center = round(pos.end / 2 + pos.start / 2); 281 282 // Update relative positions 283 updateRelatives(); 284 285 // Scrollbar 286 if ($handle.length && sbSize > 0) { 287 // Stretch scrollbar handle to represent the visible area 288 if (o.dynamicHandle) { 289 handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize); 290 handleSize = within(handleSize, o.minHandleSize, sbSize); 291 $handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px'; 292 if(handleSize!=sbSize){ 293 $($sb).parent().animate({'opacity': 1}, 50); 294 $($sb).fadeIn(); 295 $($sb).closest('.wrapp_scrollbar').find('.slider_navigation').fadeIn(); 296 if($($frame).hasClass('props')){ 297 $($frame).addClass('border'); 298 } 299 }else{ 300 $($sb).parent().animate({'opacity': 0}, 50); 301 $($sb).fadeOut(); 302 $($sb).closest('.wrapp_scrollbar').find('.slider_navigation').fadeOut(); 303 if($($frame).hasClass('props')){ 304 $($frame).removeClass('border'); 305 } 306 } 307 } else { 308 handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight'](); 309 } 310 311 hPos.end = sbSize - handleSize; 312 313 if (!renderID) { 314 syncScrollbar(); 315 } 316 } 317 318 // Pages 319 if (!parallax && frameSize > 0) { 320 var tempPagePos = pos.start; 321 var pagesHtml = ''; 322 323 // Populate pages array 324 if (itemNav) { 325 $.each(items, function (i, item) { 326 if (forceCenteredNav) { 327 pages.push(item.center); 328 } else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) { 329 tempPagePos = item.start; 330 pages.push(tempPagePos); 331 tempPagePos += frameSize; 332 if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) { 333 pages.push(pos.end); 334 } 335 } 336 }); 337 } else { 338 while (tempPagePos - frameSize < pos.end) { 339 pages.push(tempPagePos); 340 tempPagePos += frameSize; 341 } 342 } 343 344 // Pages bar 345 if ($pb[0] && lastPagesCount !== pages.length) { 346 for (var i = 0; i < pages.length; i++) { 347 pagesHtml += o.pageBuilder.call(self, i); 348 } 349 $pages = $pb.html(pagesHtml).children(); 350 $pages.eq(rel.activePage).addClass(o.activeClass); 351 } 352 } 353 354 // Extend relative variables object with some useful info 355 rel.slideeSize = slideeSize; 356 rel.frameSize = frameSize; 357 rel.sbSize = sbSize; 358 rel.handleSize = handleSize; 359 360 // Activate requested position 361 if (itemNav) { 362 if (isInit && o.startAt != null) { 363 activate(o.startAt); 364 self[centeredNav ? 'toCenter' : 'toStart'](o.startAt); 365 } 366 // Fix possible overflowing 367 var activeItem = items[rel.activeItem]; 368 slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end)); 369 } else { 370 if (isInit) { 371 if (o.startAt != null) slideTo(o.startAt, 1); 372 } else { 373 // Fix possible overflowing 374 slideTo(within(pos.dest, pos.start, pos.end)); 375 } 376 } 377 378 // Trigger load event 379 trigger('load'); 380 } 381 self.reload = function () { load(); }; 382 383 /** 384 * Animate to a position. 385 * 386 * @param {Int} newPos New position. 387 * @param {Bool} immediate Reposition immediately without an animation. 388 * @param {Bool}
388 dontAlign Do not align items, use the raw position passed in first argument. 389 * 390 * @return {Void} 391 */ 392 function slideTo(newPos, immediate, dontAlign) { 393 // Align items 394 /*if (itemNav && dragging.released && !dontAlign) { 395 var tempRel = getRelatives(newPos); 396 var isNotBordering = newPos > pos.start && newPos < pos.end; 397 398 if (centeredNav) { 399 if (isNotBordering) { 400 newPos = items[tempRel.centerItem].center; 401 } 402 if (forceCenteredNav && o.activateMiddle) { 403 activate(tempRel.centerItem); 404 } 405 } else if (isNotBordering) { 406 newPos = items[tempRel.firstItem].start; 407 } 408 }*/ 409 410 // Handle overflowing position limits 411 if (dragging.init && dragging.slidee && o.elasticBounds) { 412 if (newPos > pos.end) { 413 newPos = pos.end + (newPos - pos.end) / 6; 414 } else if (newPos < pos.start) { 415 newPos = pos.start + (newPos - pos.start) / 6; 416 } 417 } else { 418 newPos = within(newPos, pos.start, pos.end); 419 } 420 421 // Update the animation object 422 animation.start = +new Date(); 423 animation.time = 0; 424 animation.from = pos.cur; 425 animation.to = newPos; 426 animation.delta = newPos - pos.cur; 427 animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee; 428 animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed); 429 430 // Reset dragging tweesing request 431 dragging.tweese = 0; 432 433 // Start animation rendering 434 if (newPos !== pos.dest) { 435 pos.dest = newPos; 436 trigger('change'); 437 if (!renderID) { 438 render(); 439 } 440 } 441 442 // Reset next cycle timeout 443 resetCycle(); 444 445 // Synchronize states 446 updateRelatives(); 447 updateButtonsState(); 448 syncPagesbar(); 449 } 450 451 /** 452 * Render animation frame. 453 * 454 * @return {Void} 455 */ 456 function render() { 457 if (!self.initialized) { 458 return; 459 } 460 461 // If first render call, wait for next animationFrame 462 if (!renderID) { 463 renderID = rAF(render); 464 if (dragging.released) { 465 trigger('moveStart'); 466 } 467 return; 468 } 469 470 // If immediate repositioning is requested, don't animate. 471 if (animation.immediate) { 472 pos.cur = animation.to; 473 } 474 // Use tweesing for animations without known end point 475 else if (animation.tweesing) { 476 animation.tweeseDelta = animation.to - pos.cur; 477 // Fuck Zeno's paradox 478 /*if (abs(animation.tweeseDelta) < 0.1) { 479 pos.cur = animation.to; 480 } else {*/ 481 pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed); 482 //} 483 } 484 // Use tweening for basic animations with known end point 485 else { 486 animation.time = min(+new Date() - animation.start, o.speed); 487 pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed); 488 } 489 490 // If there is nothing more to render break the rendering loop, otherwise request new animation frame. 491 if (animation.to === pos.cur) { 492 pos.cur = animation.to; 493 dragging.tweese = renderID = 0; 494 } else { 495 renderID = rAF(render); 496 } 497 498 trigger('move'); 499 // Update SLIDEE position 500 if (!parallax) { 501 if (transform) { 502 $slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)'; 503 } else { 504 $slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px'; 505 } 506 } 507 508 // When animation reached the end, and dragging is not active, trigger moveEnd 509 if (!renderID && dragging.released) { 510 trigger('moveEnd'); 511 } 512 513 syncScrollbar(); 514 } 515 516 /** 517 * Synchronizes scrollbar with the SLIDEE. 518 * 519 * @return {Void} 520 */ 521 function syncScrollbar() { 522 if ($handle.length) { 523 hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end; 524 hPos.cur = within(round(hPos.cur), hPos.start, hPos.end); 525 if (last.hPos !== hPos.cur) { 526 last.hPos = hPos.cur; 527 if (transform) { 528 $handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)'; 529 } else { 530 $handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px'; 531 } 532 } 533 } 534 } 535 536 /** 537 * Synchronizes pagesbar with SLIDEE. 538 * 539 * @return {Void} 540 */ 541 function syncPagesbar() { 542 if ($pages[0] && last.page !== rel.activePage) { 543 last.page = rel.activePage; 544 $pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass); 545 trigger('activePage', last.page); 546 } 547 } 548 549 /** 550 * Returns the position object. 551 * 552 * @param {Mixed} item 553 * 554 * @return {Object} 555 */ 556 self.getPos = function (item) { 557 if (itemNav) { 558 var index = getIndex(item); 559 return index !== -1 ? items[index] : false;
560 } else { 561 var $item = $slidee.find(item).eq(0); 562 563 if ($item[0]) { 564 var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top; 565 var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight'](); 566 567 return { 568 start: offset, 569 center: offset - frameSize / 2 + size / 2, 570 end: offset - frameSize + size, 571 size: size 572 }; 573 } else { 574 return false; 575 } 576 } 577 }; 578 579 /** 580 * Continuous move in a specified direction. 581 * 582 * @param {Bool} forward True for forward movement, otherwise it'll go backwards. 583 * @param {Int} speed Movement speed in pixels per frame. Overrides options.moveBy value. 584 * 585 * @return {Void} 586 */ 587 self.moveBy = function (speed) { 588 move.speed = speed; 589 // If already initiated, or there is nowhere to move, abort 590 /*if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { 591 return; 592 }*/ 593 // Initiate move object 594 move.lastTime = +new Date();
595 move.startPos = pos.cur; 596 // Set dragging as initiated 597 continuousInit('button'); 598 dragging.init = 1; 599 // Start movement 600 trigger('moveStart'); 601 cAF(continuousID); 602 moveLoop(); 603 }; 604 605 /** 606 * Continuous movement loop. 607 * 608 * @return {Void} 609 */ 610 function moveLoop() { 611 // If there is nowhere to move anymore, stop 612 /*if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) { 613 self.stop(); 614 }*/ 615 616 // Request new move loop if it hasn't been stopped 617 continuousID = dragging.init ? rAF(moveLoop) : 0; 618 // Update move object 619 move.now = +new Date(); 620 move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed; 621 // Slide 622 slideTo(dragging.init ? move.pos : round(move.pos)); 623 // Normally, this is triggered in render(), but if there 624 // is nothing to render, we have to do it manually here. 625 if (!dragging.init && pos.cur === pos.dest) { 626 trigger('moveEnd'); 627 } 628 // Update times for future iteration 629 move.lastTime = move.now; 630 } 631 632 /** 633 * Stops continuous movement. 634 * 635 * @return {Void} 636 */ 637 self.stop = function () { 638 if (dragging.source === 'button') { 639 dragging.init = 0; 640 dragging.released = 1; 641 } 642 }; 643 644 /** 645 * Activate previous item. 646 * 647 * @return {Void} 648 */ 649 self.prev = function () { 650 self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1); 651 }; 652 653 /** 654 * Activate next item. 655 * 656 * @return {Void} 657 */ 658 self.next = function () { 659 self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1); 660 }; 661 662 /** 663 * Activate previous page. 664 * 665 * @return {Void} 666 */ 667 self.prevPage = function () { 668 self.activatePage(rel.activePage - 1); 669 }; 670 671 /** 672 * Activate next page. 673 * 674 * @return {Void} 675 */ 676 self.nextPage = function () {
677 self.activatePage(rel.activePage + 1); 678 }; 679 680 /** 681 * Slide SLIDEE by amount of pixels. 682 * 683 * @param {Int} delta Pixels/Items. Positive means forward, negative means backward. 684 * @param {Bool} immediate Reposition immediately without an animation. 685 * 686 * @return {Void} 687 */ 688 self.slideBy = function (delta, immediate) { 689 if (!delta) { 690 return; 691 } 692 if (itemNav) { 693 self[centeredNav ? 'toCenter' : 'toStart']( 694 within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length) 695 ); 696 } else { 697 slideTo(pos.dest + delta, immediate); 698 } 699 }; 700 701 /** 702 * Animate SLIDEE to a specific position. 703 * 704 * @param {Int} pos New position. 705 * @param {Bool} immediate Reposition immediately without an animation. 706 * 707 * @return {Void} 708 */ 709 self.slideTo = function (pos, immediate) { 710 slideTo(pos, immediate); 711 }; 712 713 /** 714 * Core method for handling `toLocation` methods. 715 * 716 * @param {String} location 717 * @param {Mixed} item 718 * @param {Bool} immediate 719 * 720 * @return {Void} 721 */ 722 function to(location, item, immediate) { 723 // Optional arguments logic 724 if (type(item) === 'boolean') { 725 immediate = item; 726 item = undefined; 727 } 728 729 if (item === undefined) { 730 slideTo(pos[location], immediate); 731 } else { 732 // You can't align items to sides of the frame 733 // when centered navigation type is enabled 734 if (centeredNav && location !== 'center') { 735 return; 736 } 737 738 var itemPos = self.getPos(item); 739 if (itemPos) { 740 slideTo(itemPos[location], immediate, !centeredNav); 741 } 742 } 743 } 744 745 /** 746 * Animate element or the whole SLIDEE to the start of the frame. 747 * 748 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 749 * @param {Bool} immediate Reposition immediately without an animation. 750 * 751 * @return {Void} 752 */ 753 self.toStart = function (item, immediate) { 754 to('start', item, immediate); 755 }; 756 757 /** 758 * Animate element or the whole SLIDEE to the end of the frame. 759 * 760 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 761 * @param {Bool} immediate Reposition immediately without an animation. 762 * 763 * @return {Void} 764 */ 765 self.toEnd = function (item, immediate) { 766 to('end', item, immediate); 767 }; 768 769 /** 770 * Animate element or the whole SLIDEE to the center of the frame. 771 * 772 * @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE. 773 * @param {Bool} immediate Reposition immediately without an animation. 774 * 775 * @return {Void} 776 */ 777 self.toCenter = function (item, immediate) { 778 to('center', item, immediate); 779 }; 780 781 /** 782 * Get the index of an item in SLIDEE. 783 * 784 * @param {Mixed} item Item DOM element. 785 * 786 * @return {Int} Item index, or -1 if not found. 787 */ 788 function getIndex(item) { 789 return item != null ? 790 isNumber(item) ? 791 item >= 0 && item < items.length ? item : -1 : 792 $items.index(item) : 793 -1; 794 } 795 // Expose getIndex without lowering the compressibility of it, 796 // as it is used quite often throughout Sly. 797 self.getIndex = getIndex; 798 799 /** 800 * Get index of an item in SLIDEE based on a variety of input types. 801 * 802 * @param {Mixed} item DOM element, positive or negative integer. 803 * 804 * @return {Int} Item index, or -1 if not found. 805 */ 806 function getRelativeIndex(item) { 807 return getIndex(isNumber(item) && item < 0 ? item + items.length : item); 808 } 809 810 /** 811 * Activates an item. 812 * 813 * @param {Mixed} item Item DOM element, or index starting at 0. 814 * 815 * @return {Mixed} Activated item index or false on fail. 816 */ 817 function activate(item, force) { 818 var index = getIndex(item); 819 820 if (!itemNav || index < 0) { 821 return false; 822 } 823 824 // Update classes, last active index, and trigger active event only when there 825 // has been a change. Otherwise just return the current active index. 826 if (last.active !== index || force) { 827 // Update classes 828 $items.eq(rel.activeItem).removeClass(o.activeClass); 829 $items.eq(index).addClass(o.activeClass); 830 831 last.active = rel.activeItem = index; 832 833 updateButtonsState(); 834 trigger('active', index); 835 } 836 837 return index; 838 } 839 840 /** 841 * Activates an item and helps with further navigation when o.smart is enabled. 842 * 843 * @param {Mixed} item Item DOM element, or index starting at 0. 844 * @param {Bool} immediate Whether to reposition immediately in smart navigation. 845 * 846 * @return {Void} 847 */ 848 self.activate = function (item, immediate) { 849 var index = activate(item); 850 851 // Smart navigation 852 if (o.smart && index !== false) { 853 // When centeredNav is enabled, center the element. 854 // Otherwise, determine where to position the element based on its current position. 855 // If the element is currently on the far end side of the frame, assume that user is 856 // moving forward and animate it to the start of the visible frame, and vice versa. 857 if (centeredNav) { 858 self.toCenter(index, immediate); 859 } else if (index >= rel.lastItem) { 860 self.toStart(index, immediate); 861 } else if (index <= rel.firstItem) { 862 self.toEnd(index, immediate); 863 } else { 864 resetCycle(); 865 } 866 } 867 }; 868 869 /** 870 * Activates a page. 871 * 872 * @param {Int} index Page index, starting from 0. 873 * @param {Bool} immediate Whether to reposition immediately without animation. 874 * 875 * @return {Void} 876 */ 877 self.activatePage = function (index, immediate) { 878 if (isNumber(index)) { 879 slideTo(pages[within(index, 0, pages.length - 1)], immediate); 880 } 881 }; 882 883 /** 884 * Return relative positions of items based on their visibility within FRAME. 885 * 886 * @param {Int} slideePos Position of SLIDEE. 887 * 888 * @return {Void} 889 */ 890 function getRelatives(slideePos) {
891 slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end); 892 893 var relatives = {}; 894 var centerOffset = forceCenteredNav ? 0 : frameSize / 2; 895 896 // Determine active page 897 if (!parallax) { 898 for (var p = 0, pl = pages.length; p < pl; p++) { 899 if (slideePos >= pos.end || p === pages.length - 1) { 900 relatives.activePage = pages.length - 1; 901 break; 902 } 903 904 if (slideePos <= pages[p] + centerOffset) { 905 relatives.activePage = p; 906 break; 907 } 908 } 909 } 910 911 // Relative item indexes 912 if (itemNav) { 913 var first = false; 914 var last = false; 915 var center = false; 916 917 // From start 918 for (var i = 0, il = items.length; i < il; i++) { 919 // First item 920 if (first === false && slideePos <= items[i].start + items[i].half) { 921 first = i; 922 } 923 924 // Center item 925 if (center === false && slideePos <= items[i].center + items[i].half) { 926 center = i; 927 } 928 929 // Last item 930 if (i === il - 1 || slideePos <= items[i].end + items[i].half) { 931 last = i; 932 break; 933 } 934 } 935 936 // Safe assignment, just to be sure the false won't be returned 937 relatives.firstItem = isNumber(first) ? first : 0; 938 relatives.centerItem = isNumber(center) ? center : relatives.firstItem; 939 relatives.lastItem = isNumber(last) ? last : relatives.centerItem; 940 } 941 942 return relatives; 943 } 944 945 /** 946 * Update object with relative positions. 947 * 948 * @param {Int} newPos 949 * 950 * @return {Void} 951 */ 952 function updateRelatives(newPos) { 953 $.extend(rel, getRelatives(newPos)); 954 } 955 956 /** 957 * Disable navigation buttons when needed. 958 * 959 * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state. 960 * 961 * @return {Void} 962 */ 963 function updateButtonsState() { 964 var isStart = pos.dest <= pos.start; 965 var isEnd = pos.dest >= pos.end; 966 var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0); 967 968 // Update paging buttons only if there has been a change in SLIDEE position 969 if (last.slideePosState !== slideePosState) { 970 last.slideePosState = slideePosState; 971 972 if ($prevPageButton.is('button,input')) { 973 $prevPageButton.prop('disabled', isStart); 974 } 975 976 if ($nextPageButton.is('button,input')) { 977 $nextPageButton.prop('disabled', isEnd); 978 } 979 980 $prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass); 981 $nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass); 982 } 983 984 // Forward & Backward buttons need a separate state caching because we cannot "property disable" 985 // them while they are being used, as disabled buttons stop emitting mouse events. 986 if (last.fwdbwdState !== slideePosState && dragging.released) { 987 last.fwdbwdState = slideePosState; 988 989 if ($backwardButton.is('button,input')) { 990 $backwardButton.prop('disabled', isStart); 991 } 992 993 if ($forwardButton.is('button,input')) { 994 $forwardButton.prop('disabled', isEnd); 995 } 996 } 997 998 // Item navigation 999 if (itemNav && rel.activeItem != null) { 1000 var isFirst = rel.activeItem === 0; 1001 var isLast = rel.activeItem >= items.length - 1; 1002 var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0); 1003 1004 if (last.itemsButtonState !== itemsButtonState) { 1005 last.itemsButtonState = itemsButtonState; 1006 1007 if ($prevButton.is('button,input')) { 1008 $prevButton.prop('disabled', isFirst); 1009 } 1010 1011 if ($nextButton.is('button,input')) { 1012 $nextButton.prop('disabled', isLast); 1013 } 1014 1015 $prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass); 1016 $nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass); 1017 } 1018 } 1019 } 1020 1021 /** 1022 * Resume cycling. 1023 * 1024 * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover. 1025 * 1026 * @return {Void} 1027 */ 1028 self.resume = function (priority) { 1029 if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) { 1030 return; 1031 } 1032 1033 self.isPaused = 0; 1034 1035 if (cycleID) { 1036 cycleID = clearTimeout(cycleID); 1037 } else { 1038 trigger('resume'); 1039 } 1040 1041 cycleID = setTimeout(function () { 1042 trigger('cycle'); 1043 switch (o.cycleBy) { 1044 case 'items': 1045 self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1); 1046 break; 1047 1048 case 'pages': 1049 self.activatePage(rel.activePage >
1049= pages.length - 1 ? 0 : rel.activePage + 1); 1050 break; 1051 } 1052 }, o.cycleInterval); 1053 }; 1054 1055 /** 1056 * Pause cycling. 1057 * 1058 * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover. 1059 * 1060 * @return {Void} 1061 */ 1062 self.pause = function (priority) { 1063 if (priority < self.isPaused) { 1064 return; 1065 } 1066 1067 self.isPaused = priority || 100; 1068 1069 if (cycleID) { 1070 cycleID = clearTimeout(cycleID); 1071 trigger('pause'); 1072 } 1073 }; 1074 1075 /** 1076 * Toggle cycling. 1077 * 1078 * @return {Void} 1079 */ 1080 self.toggle = function () { 1081 self[cycleID ? 'pause' : 'resume'](); 1082 }; 1083 1084 /** 1085 * Updates a signle or multiple option values. 1086 * 1087 * @param {Mixed} name Name of the option that should be updated, or object that will extend the options. 1088 * @param {Mixed} value New option value. 1089 * 1090 * @return {Void} 1091 */ 1092 self.set = function (name, value) { 1093 if ($.isPlainObject(name)) { 1094 $.extend(o, name); 1095 } else if (o.hasOwnProperty(name)) { 1096 o[name] = value; 1097 } 1098 }; 1099 1100 /** 1101 * Add one or multiple items to the SLIDEE end, or a specified position index. 1102 * 1103 * @param {Mixed} element Node element, or HTML string. 1104 * @param {Int} index Index of a new item position. By default item is appended at the end. 1105 * 1106 * @return {Void} 1107 */ 1108 self.add = function (element, index) { 1109 var $element = $(element); 1110 1111 if (itemNav) { 1112 // Insert the element(s) 1113 if (index == null || !items[0] || index >= items.length) { 1114 $element.appendTo($slidee); 1115 } else if (items.length) { 1116 $element.insertBefore(items[index].el); 1117 } 1118 1119 // Adjust the activeItem index 1120 if (rel.activeItem != null && index <= rel.activeItem) { 1121 last.active = rel.activeItem += $element.length; 1122 } 1123 } else { 1124 $slidee.append($element); 1125 } 1126 1127 // Reload 1128 load(); 1129 }; 1130 1131 /** 1132 * Remove an item from SLIDEE. 1133 * 1134 * @param {Mixed} element Item index, or DOM element. 1135 * @param {Int} index Index of a new item position. By default item is appended at the end. 1136 * 1137 * @return {Void} 1138 */ 1139 self.remove = function (element) { 1140 if (itemNav) { 1141 var index = getRelativeIndex(element); 1142 1143 if (index > -1) { 1144 // Remove the element 1145 $items.eq(index).remove(); 1146 1147 // If the current item is being removed, activate new one after reload 1148 var reactivate = index === rel.activeItem; 1149 1150 // Adjust the activeItem index 1151 if (rel.activeItem != null && index < rel.activeItem) { 1152 last.active = --rel.activeItem; 1153 } 1154 1155 // Reload 1156 load(); 1157 1158 // Activate new item at the removed position 1159 if (reactivate) { 1160 last.active = null; 1161 self.activate(rel.activeItem); 1162 } 1163 } 1164 } else { 1165 $(element).remove(); 1166 load(); 1167 } 1168 }; 1169 1170 /** 1171 * Helps re-arranging items. 1172 * 1173 * @param {Mixed} item Item DOM element, or index starting at 0. Use negative numbers to select items from the end. 1174 * @param {Mixed} position Item insertion anchor. Accepts same input types as item argument. 1175 * @param {Bool} after Insert after instead of before the anchor. 1176 * 1177 * @return {Void} 1178 */ 1179 function moveItem(item, position, after) { 1180 item = getRelativeIndex(item); 1181 position = getRelativeIndex(position); 1182 1183 // Move only if there is an actual change requested 1184 if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) { 1185 $items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el); 1186 1187 var shiftStart = item < position ? item : (after ? position : position - 1); 1188 var shiftEnd = item > position ? item : (after ? position + 1 : position); 1189 var shiftsUp = item > position; 1190 1191 // Update activeItem index 1192 if (rel.activeItem != null) { 1193 if (item === rel.activeItem) { 1194 last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1); 1195 } else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) { 1196 last.active = rel.activeItem += shiftsUp ? 1 : -1; 1197 } 1198 } 1199 1200 // Reload 1201 load(); 1202 } 1203 } 1204 1205 /** 1206 * Move item after the target anchor. 1207 * 1208 * @param {Mixed} item Item to be moved. Can be DOM element or item index. 1209 * @param {Mixed} position Target position anchor. Can be DOM element or item index. 1210 * 1211 * @return {Void} 1212 */ 1213 self.moveAfter = function (item, position) { 1214 moveItem(item, position, 1); 1215 }; 1216 1217 /** 1218 * Move item before the target anchor. 1219 * 1220 * @param {Mixed} item Item to be moved. Can be DOM element or item index. 1221 * @param {Mixed} position Target position anchor. Can be DOM element or item index. 1222 * 1223 * @return {Void} 1224 */ 1225 self.moveBefore = function (item, position) { 1226 moveItem(item, position); 1227 }; 1228 1229 /** 1230 * Registers callbacks. 1231 * 1232 * @param {Mixed}
1232 name Event name, or callbacks map. 1233 * @param {Mixed} fn Callback, or an array of callback functions. 1234 * 1235 * @return {Void} 1236 */ 1237 self.on = function (name, fn) { 1238 // Callbacks map 1239 if (type(name) === 'object') { 1240 for (var key in name) { 1241 if (name.hasOwnProperty(key)) { 1242 self.on(key, name[key]); 1243 } 1244 } 1245 // Callback 1246 } else if (type(fn) === 'function') { 1247 var names = name.split(' '); 1248 for (var n = 0, nl = names.length; n < nl; n++) { 1249 callbacks[names[n]] = callbacks[names[n]] || []; 1250 if (callbackIndex(names[n], fn) === -1) { 1251 callbacks[names[n]].push(fn); 1252 } 1253 } 1254 // Callbacks array 1255 } else if (type(fn) === 'array') { 1256 for (var f = 0, fl = fn.length; f < fl; f++) { 1257 self.on(name, fn[f]); 1258 } 1259 } 1260 }; 1261 1262 /** 1263 * Registers callbacks to be executed only once. 1264 * 1265 * @param {Mixed} name Event name, or callbacks map. 1266 * @param {Mixed} fn Callback, or an array of callback functions. 1267 * 1268 * @return {Void} 1269 */ 1270 self.one = function (name, fn) { 1271 function proxy() { 1272 fn.apply(self, arguments); 1273 self.off(name, proxy); 1274 } 1275 self.on(name, proxy); 1276 }; 1277 1278 /** 1279 * Remove one or all callbacks. 1280 * 1281 * @param {String} name Event name. 1282 * @param {Mixed} fn Callback, or an array of callback functions. Omit to remove all callbacks. 1283 * 1284 * @return {Void} 1285 */ 1286 self.off = function (name, fn) { 1287 if (fn instanceof Array) { 1288 for (var f = 0, fl = fn.length; f < fl; f++) { 1289 self.off(name, fn[f]); 1290 } 1291 } else { 1292 var names = name.split(' '); 1293 for (var n = 0, nl = names.length; n < nl; n++) { 1294 callbacks[names[n]] = callbacks[names[n]] || []; 1295 if (fn == null) { 1296 callbacks[names[n]].length = 0; 1297 } else { 1298 var index = callbackIndex(names[n], fn); 1299 if (index !== -1) { 1300 callbacks[names[n]].splice(index, 1); 1301 } 1302 } 1303 } 1304 } 1305 }; 1306 1307 /** 1308 * Returns callback array index. 1309 * 1310 * @param {String} name Event name. 1311 * @param {Function} fn Function 1312 * 1313 * @return {Int} Callback array index, or -1 if isn't registered. 1314 */ 1315 function callbackIndex(name, fn) { 1316 for (var i = 0, l = callbacks[name].length; i < l; i++) { 1317 if (callbacks[name][i] === fn) { 1318 return i; 1319 } 1320 } 1321 return -1; 1322 } 1323 1324 /** 1325 * Reset next cycle timeout. 1326 * 1327 * @return {Void} 1328 */ 1329 function resetCycle() { 1330 if (dragging.released && !self.isPaused) { 1331 self.resume(); 1332 } 1333 } 1334 1335 /** 1336 * Calculate SLIDEE representation of handle position. 1337 * 1338 * @param {Int} handlePos 1339 * 1340 * @return {Int} 1341 */ 1342 function handleToSlidee(handlePos) { 1343 return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start; 1344 } 1345 1346 /** 1347 * Keeps track of a dragging delta history. 1348 * 1349 * @return {Void} 1350 */ 1351 function draggingHistoryTick() { 1352 // Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way 1353 // as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't 1354 // generate garbage. The loop version would create 2 new variables on every tick. Unexaptable! 1355 dragging.history[0] = dragging.history[1]; 1356 dragging.history[1] = dragging.history[2]; 1357 dragging.history[2] = dragging.history[3]; 1358 dragging.history[3] = dragging.delta; 1359 } 1360 1361 /** 1362 * Initialize continuous movement. 1363 * 1364 * @return {Void} 1365 */ 1366 function continuousInit(source) { 1367 dragging.released = 0; 1368 dragging.source = source; 1369 dragging.slidee = source === 'slidee'; 1370 } 1371 1372 /** 1373 * Dragging initiator. 1374 * 1375 * @param {Event} event 1376 * 1377 * @return {Void} 1378 */ 1379 function dragInit(event) { 1380 var isTouch = event.type === 'touchstart'; 1381 var source = event.data.source; 1382 var isSlidee = source === 'slidee'; 1383 1384 // Ignore when already in progress, or interactive element in non-touch navivagion 1385 if (dragging.init || !isTouch && isInteractive(event.target)) { 1386 return; 1387 } 1388 1389 // Handle dragging conditions 1390 if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) { 1391 return; 1392 } 1393 1394 // SLIDEE dragging conditions 1395 if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) { 1396 return; 1397 } 1398 1399 if (!isTouch) { 1400 // prevents native image dragging in Firefox 1401 stopDefault(event); 1402 } 1403 1404 // Reset dragging object
1405 continuousInit(source); 1406 1407 // Properties used in dragHandler 1408 dragging.init = 0; 1409 dragging.$source = $(event.target); 1410 dragging.touch = isTouch; 1411 dragging.pointer = isTouch ? event.originalEvent.touches[0] : event; 1412 dragging.initX = dragging.pointer.pageX; 1413 dragging.initY = dragging.pointer.pageY; 1414 dragging.initPos = isSlidee ? pos.cur : hPos.cur; 1415 dragging.start = +new Date(); 1416 dragging.time = 0; 1417 dragging.path = 0; 1418 dragging.delta = 0; 1419 dragging.locked = 0; 1420 dragging.history = [0, 0, 0, 0]; 1421 dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0; 1422 1423 // Bind dragging events 1424 $doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler); 1425 1426 // Pause ongoing cycle 1427 self.pause(1); 1428 1429 // Add dragging class 1430 (isSlidee ? $slidee : $handle).addClass(o.draggedClass); 1431 1432 // Trigger moveStart event 1433 trigger('moveStart'); 1434 1435 // Keep track of a dragging path history. This is later used in the 1436 // dragging release swing calculation when dragging SLIDEE. 1437 if (isSlidee) { 1438 historyID = setInterval(draggingHistoryTick, 10); 1439 } 1440 } 1441 1442 /** 1443 * Handler for dragging scrollbar handle or SLIDEE. 1444 * 1445 * @param {Event} event 1446 * 1447 * @return {Void} 1448 */ 1449 function dragHandler(event) { 1450 dragging.released = event.type === 'mouseup' || event.type === 'touchend'; 1451 dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event; 1452 dragging.pathX = dragging.pointer.pageX - dragging.initX; 1453 dragging.pathY = dragging.pointer.pageY - dragging.initY; 1454 dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2)); 1455 dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY; 1456 1457 if (!dragging.released && dragging.path < 1) return; 1458 1459 if (!dragging.init) { 1460 if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) { 1461 dragging.init = 1; 1462 } else { 1463 return dragEnd(); 1464 } 1465 } 1466 1467 stopDefault(event); 1468 1469 // Disable click on a source element, as it is unwelcome when dragging 1470 if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) { 1471 dragging.locked = 1; 1472 dragging.$source.on(clickEvent, disableOneEvent); 1473 } 1474 1475 // Cancel dragging on release 1476 if (dragging.released) { 1477 dragEnd(); 1478 1479 // Adjust path with a swing on mouse release 1480 if (o.releaseSwing && dragging.slidee) { 1481 dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300; 1482 dragging.delta += dragging.swing; 1483 dragging.tweese = abs(dragging.swing) > 10; 1484 } 1485 } 1486 1487 slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta)); 1488 } 1489 1490 /** 1491 * Stops dragging and cleans up after it. 1492 * 1493 * @return {Void} 1494 */ 1495 function dragEnd() { 1496 clearInterval(historyID); 1497 dragging.released = true; 1498 $doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler); 1499 (dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass); 1500 1501 // Make sure that disableOneEvent is not active in next tick. 1502 setTimeout(function () { 1503 dragging.$source.off(clickEvent, disableOneEvent); 1504 }); 1505 1506 // Normally, this is triggered in render(), but if there 1507 // is nothing to render, we have to do it manually here. 1508 if (pos.cur === pos.dest && dragging.init) { 1509 trigger('moveEnd'); 1510 } 1511 1512 // Resume ongoing cycle 1513 self.resume(1); 1514 1515 dragging.init = 0; 1516 } 1517 1518 /** 1519 * Check whether element is interactive. 1520 * 1521 * @return {Boolean} 1522 */ 1523 function isInteractive(element) { 1524 return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive); 1525 } 1526 1527 /** 1528 * Continuous movement cleanup on mouseup. 1529 * 1530 * @return {Void} 1531 */ 1532 function movementReleaseHandler() { 1533 self.stop(); 1534 //$doc.off('mouseup', movementReleaseHandler); 1535 } 1536 1537 /** 1538 * Buttons navigation handler. 1539 * 1540 * @param {Event} event 1541 * 1542 * @return {Void} 1543 */ 1544 function buttonsHandler(event) { 1545 /*jshint validthis:true */ 1546 stopDefault(event); 1547 switch (this) { 1548 case $forwardButton[0]: 1549 case $backwardButton[0]: 1550 self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy); 1551 $doc.on('mouseup', movementReleaseHandler); 1552 break; 1553
1554 case $prevButton[0]: 1555 self.prev(); 1556 break; 1557 1558 case $nextButton[0]: 1559 self.next(); 1560 break; 1561 1562 case $prevPageButton[0]: 1563 self.prevPage(); 1564 break; 1565 1566 case $nextPageButton[0]: 1567 self.nextPage(); 1568 break; 1569 } 1570 } 1571 1572 /** 1573 * Mouse wheel delta normalization. 1574 * 1575 * @param {Event} event 1576 * 1577 * @return {Int} 1578 */ 1579 function normalizeWheelDelta(event) { 1580 // wheelDelta needed only for IE8- 1581 scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta); 1582 scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100; 1583 if (!itemNav) { 1584 return scrolling.curDelta; 1585 } 1586 time = +new Date(); 1587 if (scrolling.last < time - scrolling.resetTime) { 1588 scrolling.delta = 0; 1589 } 1590 scrolling.last = time; 1591 scrolling.delta += scrolling.curDelta; 1592 if (abs(scrolling.delta) < 1) { 1593 scrolling.finalDelta = 0; 1594 } else { 1595 scrolling.finalDelta = round(scrolling.delta / 1); 1596 scrolling.delta %= 1; 1597 } 1598 return scrolling.finalDelta; 1599 } 1600 1601 /** 1602 * Mouse scrolling handler. 1603 * 1604 * @param {Event} event 1605 * 1606 * @return {Void} 1607 */ 1608 function scrollHandler(event) { 1609 // Mark event as originating in a Sly instance 1610 event.originalEvent[namespace] = self; 1611 // Don't hijack global scrolling 1612 var time = +new Date(); 1613 if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) { 1614 lastGlobalWheel = time; 1615 return; 1616 } 1617 // Ignore if there is no scrolling to be done 1618 if (!o.scrollBy || pos.start === pos.end) { 1619 return; 1620 } 1621 var delta = normalizeWheelDelta(event.originalEvent); 1622 // Trap scrolling only when necessary and/or requested 1623 if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { 1624 stopDefault(event, 1); 1625 } 1626 self.slideBy(o.scrollBy * delta); 1627 } 1628 1629 /** 1630 * Scrollbar click handler. 1631 * 1632 * @param {Event} event 1633 * 1634 * @return {Void} 1635 */ 1636 function scrollbarHandler(event) { 1637 // Only clicks on scroll bar. Ignore the handle. 1638 if (o.clickBar && event.target === $sb[0]) { 1639 stopDefault(event); 1640 // Calculate new handle position and sync SLIDEE to it 1641 slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2)); 1642 } 1643 } 1644 1645 /** 1646 * Keyboard input handler. 1647 * 1648 * @param {Event} event 1649 * 1650 * @return {Void} 1651 */ 1652 function keyboardHandler(event) { 1653 if (!o.keyboardNavBy) { 1654 return; 1655 } 1656 1657 switch (event.which) { 1658 // Left or Up 1659 case o.horizontal ? 37 : 38: 1660 stopDefault(event); 1661 self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev'](); 1662 break; 1663 1664 // Right or Down 1665 case o.horizontal ? 39 : 40: 1666 stopDefault(event); 1667 self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next'](); 1668 break; 1669 } 1670 } 1671 1672 /** 1673 * Click on item activation handler. 1674 * 1675 * @param {Event} event 1676 * 1677 * @return {Void} 1678 */ 1679 function activateHandler(event) { 1680 /*jshint validthis:true */ 1681 1682 // Ignore clicks on interactive elements. 1683 if (isInteractive(this)) { 1684 event.originalEvent[namespace + 'ignore'] = true; 1685 return; 1686 } 1687 1688 // Ignore events that: 1689 // - are not originating from direct SLIDEE children 1690 // - originated from interactive elements 1691 if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return; 1692 1693 self.activate(this); 1694 } 1695 1696 /** 1697 * Click on page button handler. 1698 * 1699 * @param {Event} event 1700 * 1701 * @return {Void} 1702 */ 1703 function activatePageHandler() { 1704 /*jshint validthis:true */ 1705 // Accept only events from direct pages bar children. 1706 if (this.parentNode === $pb[0]) { 1707 self.activatePage($pages.index(this)); 1708 } 1709 } 1710 1711 /** 1712 * Pause on hover handler. 1713 * 1714 * @param {Event} event 1715 * 1716 * @return {Void} 1717 */ 1718 function pauseOnHoverHandler(event) { 1719 if (o.pauseOnHover) { 1720 self[event.type === 'mouseenter' ? 'pause' : 'resume'](2); 1721 } 1722 } 1723 1724 /** 1725 * Trigger callbacks for event. 1726 * 1727 * @param {String} name Event name. 1728 * @param {Mixed} argX Arguments passed to callbacks. 1729 * 1730 * @return {Void} 1731 */ 1732 function trigger(name, arg1) { 1733 if (callbacks[name]) { 1734 l = callbacks[name].length; 1735 // Callbacks will be stored and executed from a temporary array to not
1736 // break the execution queue when one of the callbacks unbinds itself. 1737 tmpArray.length = 0; 1738 for (i = 0; i < l; i++) { 1739 tmpArray.push(callbacks[name][i]); 1740 } 1741 // Execute the callbacks 1742 for (i = 0; i < l; i++) { 1743 tmpArray[i].call(self, name, arg1); 1744 } 1745 } 1746 } 1747 1748 /** 1749 * Destroys instance and everything it created. 1750 * 1751 * @return {Void} 1752 */ 1753 self.destroy = function () { 1754 // Unbind all events 1755 $scrollSource 1756 .add($handle) 1757 .add($sb) 1758 .add($pb) 1759 .add($forwardButton) 1760 .add($backwardButton) 1761 .add($prevButton) 1762 .add($nextButton) 1763 .add($prevPageButton) 1764 .add($nextPageButton) 1765 .off('.' + namespace); 1766 1767 // Unbinding specifically as to not nuke out other instances 1768 $doc.off('keydown', keyboardHandler); 1769 1770 // Remove classes 1771 $prevButton 1772 .add($nextButton) 1773 .add($prevPageButton) 1774 .add($nextPageButton) 1775 .removeClass(o.disabledClass); 1776 1777 if ($items && rel.activeItem != null) { 1778 $items.eq(rel.activeItem).removeClass(o.activeClass); 1779 } 1780 1781 // Remove page items 1782 $pb.empty(); 1783 1784 if (!parallax) { 1785 // Unbind events from frame 1786 $frame.off('.' + namespace); 1787 // Restore original styles 1788 frameStyles.restore(); 1789 slideeStyles.restore(); 1790 sbStyles.restore(); 1791 handleStyles.restore(); 1792 // Remove the instance from element data storage 1793 $.removeData(frame, namespace); 1794 } 1795 1796 // Clean up collections 1797 items.length = pages.length = 0; 1798 last = {}; 1799 1800 // Reset initialized status and return the instance 1801 self.initialized = 0; 1802 return self; 1803 }; 1804 1805 /** 1806 * Initialize. 1807 * 1808 * @return {Object} 1809 */ 1810 self.init = function () { 1811 if (self.initialized) { 1812 return; 1813 } 1814 1815 // Register callbacks map 1816 self.on(callbackMap); 1817 1818 // Save styles 1819 var holderProps = ['overflow', 'position']; 1820 var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height']; 1821 frameStyles.save.apply(frameStyles, holderProps); 1822 sbStyles.save.apply(sbStyles, holderProps); 1823 slideeStyles.save.apply(slideeStyles, movableProps); 1824 handleStyles.save.apply(handleStyles, movableProps); 1825 1826 // Set required styles 1827 var $movables = $handle; 1828 if (!parallax) { 1829 $movables = $movables.add($slidee); 1830 $frame.css('overflow', 'hidden'); 1831 if (!transform && $frame.css('position') === 'static') { 1832 $frame.css('position', 'relative'); 1833 } 1834 } 1835 if (transform) { 1836 if (gpuAcceleration) { 1837 $movables.css(transform, gpuAcceleration); 1838 } 1839 } else { 1840 if ($sb.css('position') === 'static') { 1841 $sb.css('position', 'relative'); 1842 } 1843 $movables.css({ position: 'absolute' }); 1844 } 1845 1846 // Navigation buttons 1847 if (o.forward) { 1848 $forwardButton.on(mouseDownEvent, buttonsHandler); 1849 } 1850 if (o.backward) { 1851 $backwardButton.on(mouseDownEvent, buttonsHandler); 1852 } 1853 if (o.prev) { 1854 $prevButton.on(clickEvent, buttonsHandler); 1855 } 1856 if (o.next) { 1857 $nextButton.on(clickEvent, buttonsHandler); 1858 } 1859 if (o.prevPage) { 1860 $prevPageButton.on(clickEvent, buttonsHandler); 1861 } 1862 if (o.nextPage) { 1863 $nextPageButton.on(clickEvent, buttonsHandler); 1864 } 1865 1866 // Scrolling navigation 1867 //$scrollSource.on(wheelEvent, scrollHandler); 1868 1869 // Clicking on scrollbar navigation 1870 if ($sb[0]) { 1871 $sb.on(clickEvent, scrollbarHandler); 1872 } 1873 1874 // Click on items navigation 1875 if (itemNav && o.activateOn) { 1876 $frame.on(o.activateOn + '.' + namespace, '*', activateHandler); 1877 } 1878 1879 // Pages navigation 1880 if ($pb[0] && o.activatePageOn) { 1881 $pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler); 1882 } 1883 1884 // Dragging navigation 1885 $dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit); 1886 1887 // Scrollbar dragging navigation 1888 if ($handle) { 1889 $handle.on(dragInitEvents, { source: 'handle' }, dragInit); 1890 } 1891 1892 // Keyboard navigation 1893 $doc.on('keydown', keyboardHandler); 1894 1895 if (!parallax) { 1896 // Pause on hover 1897 $frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler); 1898 // Reset native FRAME element scroll 1899 $frame.on('scroll.' + namespace, resetScroll); 1900 } 1901 1902 // Mark instance as initialized 1903 self.initialized = 1; 1904 1905 // Load 1906 load(true); 1907 1908 // Initiate automatic cycling 1909 if (o.cycleBy && !parallax) { 1910 self[o.startPaused ? 'pause' : 'resume'](); 1911 } 1912 1913 // Return instance 1914 return self; 1915 }; 1916 } 1917 1918 /** 1919 * Return type of the value. 1920 * 1921 * @param {Mixed} value 1922 * 1923 * @return {String} 1924 */ 1925 function type(value) { 1926 if (value == null) { 1927 return String(value); 1928 } 1929 1930 if (typeof value === 'object' || typeof value === 'function') { 1931 return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object'; 1932 } 1933 1934 return typeof value; 1935 } 1936 1937 /** 1938 * Event preventDefault & stopPropagation helper. 1939 * 1940 * @param {Event} event Event object. 1941 * @param {Bool} noBubbles Cancel event bubbling. 1942 * 1943 * @return {Void} 1944 */ 1945 function stopDefault(event, noBubbles) { 1946 event.preventDefault(); 1947 if (noBubbles) { 1948 event.stopPropagation(); 1949 } 1950 } 1951
1952 /** 1953 * Disables an event it was triggered on and unbinds itself. 1954 * 1955 * @param {Event} event 1956 * 1957 * @return {Void} 1958 */ 1959 function disableOneEvent(event) { 1960 /*jshint validthis:true */ 1961 stopDefault(event, 1); 1962 $(this).off(event.type, disableOneEvent); 1963 } 1964 1965 /** 1966 * Resets native element scroll values to 0. 1967 * 1968 * @return {Void} 1969 */ 1970 function resetScroll() { 1971 /*jshint validthis:true */ 1972 this.scrollLeft = 0; 1973 this.scrollTop = 0; 1974 } 1975 1976 /** 1977 * Check if variable is a number. 1978 * 1979 * @param {Mixed} value 1980 * 1981 * @return {Boolean} 1982 */ 1983 function isNumber(value) { 1984 return !isNaN(parseFloat(value)) && isFinite(value); 1985 } 1986 1987 /** 1988 * Parse style to pixels. 1989 * 1990 * @param {Object} $item jQuery object with element. 1991 * @param {Property} property CSS property to get the pixels from. 1992 * 1993 * @return {Int} 1994 */ 1995 function getPx($item, property) { 1996 return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, '')); 1997 } 1998 1999 /** 2000 * Make sure that number is within the limits. 2001 * 2002 * @param {Number} number 2003 * @param {Number} min 2004 * @param {Number} max 2005 * 2006 * @return {Number} 2007 */ 2008 function within(number, min, max) { 2009 return number < min ? min : number > max ? max : number; 2010 } 2011 2012 /** 2013 * Saves element styles for later restoration. 2014 * 2015 * Example: 2016 * var styles = new StyleRestorer(frame); 2017 * styles.save('position'); 2018 * element.style.position = 'absolute'; 2019 * styles.restore(); // restores to state before the assignment above 2020 * 2021 * @param {Element} element 2022 */ 2023 function StyleRestorer(element) { 2024 var self = {}; 2025 self.style = {}; 2026 self.save = function () { 2027 if (!element || !element.nodeType) return; 2028 for (var i = 0; i < arguments.length; i++) { 2029 self.style[arguments[i]] = element.style[arguments[i]]; 2030 } 2031 return self; 2032 }; 2033 self.restore = function () { 2034 if (!element || !element.nodeType) return; 2035 for (var prop in self.style) { 2036 if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop]; 2037 } 2038 return self; 2039 }; 2040 return self; 2041 } 2042 2043 // Local WindowAnimationTiming interface polyfill 2044 (function (w) { 2045 rAF = w.requestAnimationFrame 2046 || w.webkitRequestAnimationFrame 2047 || fallback; 2048 2049 /** 2050 * Fallback implementation. 2051 */ 2052 var prev = new Date().getTime(); 2053 function fallback(fn) { 2054 var curr = new Date().getTime(); 2055 var ms = Math.max(0, 16 - (curr - prev)); 2056 var req = setTimeout(fn, ms); 2057 prev = curr; 2058 return req; 2059 } 2060 2061 /** 2062 * Cancel. 2063 */ 2064 var cancel = w.cancelAnimationFrame 2065 || w.webkitCancelAnimationFrame 2066 || w.clearTimeout; 2067 2068 cAF = function(id){ 2069 cancel.call(w, id); 2070 }; 2071 }(window)); 2072 2073 // Feature detects 2074 (function () { 2075 var prefixes = ['', 'webkit', 'moz', 'ms', 'o']; 2076 var el = document.createElement('div'); 2077 2078 function testProp(prop) { 2079 for (var p = 0, pl = prefixes.length; p < pl; p++) { 2080 var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop; 2081 if (el.style[prefixedProp] != null) { 2082 return prefixedProp; 2083 } 2084 } 2085 } 2086 2087 // Global support indicators 2088 transform = testProp('transform'); 2089 gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : ''; 2090 }()); 2091 2092 // Expose class globally 2093 w[className] = Sly; 2094 2095 // jQuery proxy 2096 $.fn[pluginName] = function (options, callbackMap) { 2097 var method, methodArgs; 2098 2099 // Attributes logic 2100 if (!$.isPlainObject(options)) { 2101 if (type(options) === 'string' || options === false) { 2102 method = options === false ? 'destroy' : options; 2103 methodArgs = Array.prototype.slice.call(arguments, 1); 2104 } 2105 options = {}; 2106 } 2107 2108 // Apply to all elements 2109 return this.each(function (i, element) { 2110 // Call with prevention against multiple instantiations 2111 var plugin = $.data(element, namespace); 2112 2113 if (!plugin && !method) { 2114 // Create a new object if it doesn't exist yet 2115 plugin = $.data(element, namespace, new Sly(element, options, callbackMap).init()); 2116 } else if (plugin && method) { 2117 // Call method 2118 if (plugin[method]) { 2119 plugin[method].apply(plugin, methodArgs); 2120 } 2121 } 2122 }); 2123 }; 2124 2125 // Default options 2126 Sly.defaults = { 2127 slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE. 2128 horizontal: false, // Switch to horizontal mode. 2129 2130 // Item based navigation 2131 itemNav: null, // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'. 2132 itemSelector: null, // Select only items that match this selector. 2133 smart: false, // Repositions the activated item to help with further navigation. 2134 activateOn: null, // Activate an item on this event. Can be: 'click', 'mouseenter', ... 2135 activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only. 2136 2137 // Scrolling 2138 scrollSource: null, // Element for catching the mouse wheel scrolling. Default is FRAME. 2139 scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling. 2140 scrollHijack: 300, // Milliseconds since last wheel event after which it is acceptable to hijack global scroll. 2141 scrollTrap: false, // Don't bubble scrolling when hitting scrolling limits. 2142 2143 // Dragging 2144 dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME. 2145 mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor. 2146 touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events. 2147 releaseSwing: false, // Ease out on dragging swing release. 2148 swingSpeed: 0.2, // Swing synchronization speed, where: 1 = instant, 0 = infinite. 2149 elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries. 2150 interactive: null, // Selector for special interactive elements. 2151 2152 // Scrollbar 2153 scrollBar: null, // Selector or DOM element for scrollbar container. 2154 dragHandle: false, // Whether the scrollbar handle should be draggable. 2155 dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content. 2156 minHandleSize: 50, // Minimal height or width (depends on sly direction) of a handle in pixels. 2157 clickBar: false, // Enable navigation by clicking on scrollbar. 2158 syncSpeed: 0.5, // Handle =>
2158 SLIDEE synchronization speed, where: 1 = instant, 0 = infinite. 2159 2160 // Pagesbar 2161 pagesBar: null, // Selector or DOM element for pages bar container. 2162 activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ... 2163 pageBuilder: // Page item generator. 2164 function (index) { 2165 return '<li>' + (index + 1) + '</li>'; 2166 }, 2167 2168 // Navigation buttons 2169 forward: null, // Selector or DOM element for "forward movement" button. 2170 backward: null, // Selector or DOM element for "backward movement" button. 2171 prev: null, // Selector or DOM element for "previous item" button. 2172 next: null, // Selector or DOM element for "next item" button. 2173 prevPage: null, // Selector or DOM element for "previous page" button. 2174 nextPage: null, // Selector or DOM element for "next page" button. 2175 2176 // Automated cycling 2177 cycleBy: null, // Enable automatic cycling by 'items' or 'pages'. 2178 cycleInterval: 5000, // Delay between cycles in milliseconds. 2179 pauseOnHover: false, // Pause cycling when mouse hovers over the FRAME. 2180 startPaused: false, // Whether to start in paused sate. 2181 2182 // Mixed options 2183 moveBy: 300, // Speed in pixels per second used by forward and backward buttons. 2184 speed: 0, // Animations speed in milliseconds. 0 to disable animations. 2185 easing: 'swing', // Easing for duration based (tweening) animations. 2186 startAt: null, // Starting offset in pixels or items. 2187 keyboardNavBy: null, // Enable keyboard navigation by 'items' or 'pages'. 2188 2189 // Classes 2190 draggedClass: 'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle). 2191 activeClass: 'active', // Class for active items and pages. 2192 disabledClass: 'disabled' // Class for disabled navigation elements. 2193 }; 2194}(jQuery, window));
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.