Created
November 5, 2014 10:45
-
-
Save jtomaszewski/580c11a35b7510bb220e to your computer and use it in GitHub Desktop.
A cloned `ionic.views.Slider` and `slideBox` directive to `ionic.views.verticalSlider` and `verticalSlideBox`: it was done just by renaming "x, y, left, right" strings into "y, x, top, bottom". It works correctly ;)
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
var IonicModule = angular.module('ionic'), | |
extend = angular.extend, | |
forEach = angular.forEach, | |
isDefined = angular.isDefined, | |
isString = angular.isString, | |
jqLite = angular.element; | |
/** | |
* @ngdoc directive | |
* @name ionVerticalSlideBox | |
* @module ionic | |
* @delegate ionic.service:$ionicSlideBoxDelegate | |
* @restrict E | |
* @description | |
* The Slide Box is a multi-page container where each page can be swiped or dragged between: | |
* | |
* ![SlideBox](http://ionicframework.com.s3.amazonaws.com/docs/controllers/slideBox.gif) | |
* | |
* @usage | |
* ```html | |
* <ion-vertical-slide-box on-slide-changed="slideHasChanged($index)"> | |
* <ion-vertical-slide> | |
* <div class="box blue"><h1>BLUE</h1></div> | |
* </ion-vertical-slide> | |
* <ion-vertical-slide> | |
* <div class="box yellow"><h1>YELLOW</h1></div> | |
* </ion-vertical-slide> | |
* <ion-vertical-slide> | |
* <div class="box pink"><h1>PINK</h1></div> | |
* </ion-vertical-slide> | |
* </ion-vertical-slide-box> | |
* ``` | |
* | |
* @param {string=} delegate-handle The handle used to identify this slideBox | |
* with {@link ionic.service:$ionicSlideBoxDelegate}. | |
* @param {boolean=} does-continue Whether the slide box should automatically slide. | |
* @param {number=} slide-interval How many milliseconds to wait to change slides (if does-continue is true). Defaults to 4000. | |
* @param {boolean=} show-pager Whether a pager should be shown for this slide box. | |
* @param {expression=} pager-click Expression to call when a pager is clicked (if show-pager is true). Is passed the 'index' variable. | |
* @param {expression=} on-slide-changed Expression called whenever the slide is changed. Is passed an '$index' variable. | |
* @param {expression=} active-slide Model to bind the current slide to. | |
*/ | |
IonicModule | |
.directive('ionVerticalSlideBox', [ | |
'$timeout', | |
'$compile', | |
'$ionicSlideBoxDelegate', | |
function($timeout, $compile, $ionicSlideBoxDelegate) { | |
return { | |
restrict: 'E', | |
replace: true, | |
transclude: true, | |
scope: { | |
doesContinue: '@', | |
slideInterval: '@', | |
showPager: '@', | |
pagerClick: '&', | |
disableScroll: '@', | |
onSlideChanged: '&', | |
activeSlide: '=?' | |
}, | |
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) { | |
var _this = this; | |
var continuous = $scope.$eval($scope.doesContinue) === true; | |
var slideInterval = continuous ? $scope.$eval($scope.slideInterval) || 4000 : 0; | |
var slider = new ionic.views.VerticalSlider({ | |
el: $element[0], | |
auto: slideInterval, | |
continuous: continuous, | |
startSlide: $scope.activeSlide, | |
slidesChanged: function() { | |
$scope.currentSlide = slider.currentIndex(); | |
// Try to trigger a digest | |
$timeout(function() {}); | |
}, | |
callback: function(slideIndex) { | |
$scope.currentSlide = slideIndex; | |
$scope.onSlideChanged({ index: $scope.currentSlide, $index: $scope.currentSlide}); | |
$scope.$parent.$broadcast('slideBox.slideChanged', slideIndex); | |
$scope.activeSlide = slideIndex; | |
// Try to trigger a digest | |
$timeout(function() {}); | |
} | |
}); | |
slider.enableSlide($scope.$eval($attrs.disableScroll) !== true); | |
$scope.$watch('activeSlide', function(nv) { | |
if(angular.isDefined(nv)){ | |
slider.slide(nv); | |
} | |
}); | |
$scope.$on('slideBox.nextSlide', function() { | |
slider.next(); | |
}); | |
$scope.$on('slideBox.prevSlide', function() { | |
slider.prev(); | |
}); | |
$scope.$on('slideBox.setSlide', function(e, index) { | |
slider.slide(index); | |
}); | |
//Exposed for testing | |
this.__slider = slider; | |
var deregisterInstance = $ionicSlideBoxDelegate._registerInstance(slider, $attrs.delegateHandle); | |
$scope.$on('$destroy', deregisterInstance); | |
this.slidesCount = function() { | |
return slider.slidesCount(); | |
}; | |
this.onPagerClick = function(index) { | |
console.log('pagerClick', index); | |
$scope.pagerClick({index: index}); | |
}; | |
$timeout(function() { | |
slider.load(); | |
}); | |
}], | |
template: '<div class="slider">' + | |
'<div class="slider-slides" ng-transclude>' + | |
'</div>' + | |
'</div>', | |
link: function($scope, $element, $attr, slideBoxCtrl) { | |
// If the pager should show, append it to the slide box | |
if($scope.$eval($scope.showPager) !== false) { | |
var childScope = $scope.$new(); | |
var pager = jqLite('<ion-pager></ion-pager>'); | |
$element.append(pager); | |
$compile(pager)(childScope); | |
} | |
} | |
}; | |
}]) | |
.directive('ionVerticalSlide', function() { | |
return { | |
restrict: 'E', | |
require: '^ionVerticalSlideBox', | |
compile: function(element, attr) { | |
element.addClass('slider-slide'); | |
return function($scope, $element, $attr) { | |
}; | |
}, | |
}; | |
}) | |
.directive('ionVerticalPager', function() { | |
return { | |
restrict: 'E', | |
replace: true, | |
require: '^ionVerticalSlideBox', | |
template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}" ng-click="pagerClick($index)"><i class="icon ion-record"></i></span></div>', | |
link: function($scope, $element, $attr, slideBox) { | |
var selectPage = function(index) { | |
var children = $element[0].children; | |
var length = children.length; | |
for(var i = 0; i < length; i++) { | |
if(i == index) { | |
children[i].classList.add('active'); | |
} else { | |
children[i].classList.remove('active'); | |
} | |
} | |
}; | |
$scope.pagerClick = function(index) { | |
slideBox.onPagerClick(index); | |
}; | |
$scope.numSlides = function() { | |
return new Array(slideBox.slidesCount()); | |
}; | |
$scope.$watch('currentSlide', function(v) { | |
selectPage(v); | |
}); | |
} | |
}; | |
}); |
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
// AFAIR, it's based on ionic v1.0.0-beta.11 | |
(function(ionic) { | |
'use strict'; | |
ionic.views.VerticalSlider = ionic.views.View.inherit({ | |
initialize: function (options) { | |
var slider = this; | |
// utilities | |
var noop = function() {}; // simple no operation function | |
var offloadFn = function(fn) { setTimeout(fn || noop, 0); }; // offload a functions execution | |
// check browser capabilities | |
var browser = { | |
addEventListener: !!window.addEventListener, | |
touch: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch, | |
transitions: (function(temp) { | |
var props = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition']; | |
for ( var i in props ) if (temp.style[ props[i] ] !== undefined) return true; | |
return false; | |
})(document.createElement('swipe')) | |
}; | |
var container = options.el; | |
// quit if no root element | |
if (!container) return; | |
var element = container.children[0]; | |
var slides, slidePos, height, length; | |
options = options || {}; | |
var index = parseInt(options.startSlide, 10) || 0; | |
var speed = options.speed || 300; | |
options.continuous = options.continuous !== undefined ? options.continuous : true; | |
function setup() { | |
// cache slides | |
slides = element.children; | |
length = slides.length; | |
// set continuous to false if only one slide | |
if (slides.length < 2) options.continuous = false; | |
//special case if two slides | |
if (browser.transitions && options.continuous && slides.length < 3) { | |
element.appendChild(slides[0].cloneNode(true)); | |
element.appendChild(element.children[1].cloneNode(true)); | |
slides = element.children; | |
} | |
// create an array to store current positions of each slide | |
slidePos = new Array(slides.length); | |
// determine height of each slide | |
height = container.getBoundingClientRect().height || container.offsetHeight; | |
element.style.height = (slides.length * height) + 'px'; | |
// stack elements | |
var pos = slides.length; | |
while(pos--) { | |
var slide = slides[pos]; | |
slide.style.height = height + 'px'; | |
slide.setAttribute('data-index', pos); | |
if (browser.transitions) { | |
slide.style.top = (pos * -height) + 'px'; | |
move(pos, index > pos ? -height : (index < pos ? height : 0), 0); | |
} | |
} | |
// reposition elements before and after index | |
if (options.continuous && browser.transitions) { | |
move(circle(index-1), -height, 0); | |
move(circle(index+1), height, 0); | |
} | |
if (!browser.transitions) element.style.top = (index * -height) + 'px'; | |
container.style.visibility = 'visible'; | |
options.slidesChanged && options.slidesChanged(); | |
} | |
function prev() { | |
if (options.continuous) slide(index-1); | |
else if (index) slide(index-1); | |
} | |
function next() { | |
if (options.continuous) slide(index+1); | |
else if (index < slides.length - 1) slide(index+1); | |
} | |
function circle(index) { | |
// a simple positive modulo using slides.length | |
return (slides.length + (index % slides.length)) % slides.length; | |
} | |
function slide(to, slideSpeed) { | |
// do nothing if already on requested slide | |
if (index == to) return; | |
if (browser.transitions) { | |
var direction = Math.abs(index-to) / (index-to); // 1: backward, -1: forward | |
// get the actual position of the slide | |
if (options.continuous) { | |
var natural_direction = direction; | |
direction = -slidePos[circle(to)] / height; | |
// if going forward but to < index, use to = slides.length + to | |
// if going backward but to > index, use to = -slides.length + to | |
if (direction !== natural_direction) to = -direction * slides.length + to; | |
} | |
var diff = Math.abs(index-to) - 1; | |
// move all the slides between index and to in the bottom direction | |
while (diff--) move( circle((to > index ? to : index) - diff - 1), height * direction, 0); | |
to = circle(to); | |
move(index, height * direction, slideSpeed || speed); | |
move(to, 0, slideSpeed || speed); | |
if (options.continuous) move(circle(to - direction), -(height * direction), 0); // we need to get the next in place | |
} else { | |
to = circle(to); | |
animate(index * -height, to * -height, slideSpeed || speed); | |
//no fallback for a circular continuous if the browser does not accept transitions | |
} | |
index = to; | |
offloadFn(options.callback && options.callback(index, slides[index])); | |
} | |
function move(index, dist, speed) { | |
translate(index, dist, speed); | |
slidePos[index] = dist; | |
} | |
function translate(index, dist, speed) { | |
var slide = slides[index]; | |
var style = slide && slide.style; | |
if (!style) return; | |
style.webkitTransitionDuration = | |
style.MozTransitionDuration = | |
style.msTransitionDuration = | |
style.OTransitionDuration = | |
style.transitionDuration = speed + 'ms'; | |
style.webkitTransform = 'translate(0,' + dist + 'px)' + 'translateZ(0)'; | |
style.msTransform = | |
style.MozTransform = | |
style.OTransform = 'translateY(' + dist + 'px)'; | |
} | |
function animate(from, to, speed) { | |
// if not an animation, just reposition | |
if (!speed) { | |
element.style.top = to + 'px'; | |
return; | |
} | |
var start = +new Date(); | |
var timer = setInterval(function() { | |
var timeElap = +new Date() - start; | |
if (timeElap > speed) { | |
element.style.top = to + 'px'; | |
if (delay) begin(); | |
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); | |
clearInterval(timer); | |
return; | |
} | |
element.style.top = (( (to - from) * (Math.floor((timeElap / speed) * 100) / 100) ) + from) + 'px'; | |
}, 4); | |
} | |
// setup auto slideshow | |
var delay = options.auto || 0; | |
var interval; | |
function begin() { | |
interval = setTimeout(next, delay); | |
} | |
function stop() { | |
delay = options.auto || 0; | |
clearTimeout(interval); | |
} | |
// setup initial vars | |
var start = {}; | |
var delta = {}; | |
var isScrolling; | |
// setup event capturing | |
var events = { | |
handleEvent: function(event) { | |
if(event.type == 'mousedown' || event.type == 'mouseup' || event.type == 'mousemove') { | |
event.touches = [{ | |
pageY: event.pageY, | |
pageX: event.pageX | |
}]; | |
} | |
switch (event.type) { | |
case 'mousedown': this.start(event); break; | |
case 'touchstart': this.start(event); break; | |
case 'touchmove': this.touchmove(event); break; | |
case 'mousemove': this.touchmove(event); break; | |
case 'touchend': offloadFn(this.end(event)); break; | |
case 'mouseup': offloadFn(this.end(event)); break; | |
case 'webkitTransitionEnd': | |
case 'msTransitionEnd': | |
case 'oTransitionEnd': | |
case 'otransitionend': | |
case 'transitionend': offloadFn(this.transitionEnd(event)); break; | |
case 'resize': offloadFn(setup); break; | |
} | |
if (options.stopPropagation) event.stopPropagation(); | |
}, | |
start: function(event) { | |
var touches = event.touches[0]; | |
// measure start values | |
start = { | |
// get initial touch coords | |
y: touches.pageY, | |
x: touches.pageX, | |
// store time to determine touch duration | |
time: +new Date() | |
}; | |
// used for testing first move event | |
isScrolling = undefined; | |
// reset delta and end measurements | |
delta = {}; | |
// attach touchmove and touchend listeners | |
if(browser.touch) { | |
element.addEventListener('touchmove', this, false); | |
element.addEventListener('touchend', this, false); | |
} else { | |
element.addEventListener('mousemove', this, false); | |
element.addEventListener('mouseup', this, false); | |
document.addEventListener('mouseup', this, false); | |
} | |
}, | |
touchmove: function(event) { | |
// ensure swiping with one touch and not pinching | |
// ensure sliding is enabled | |
if (event.touches.length > 1 || | |
event.scale && event.scale !== 1 || | |
slider.slideIsDisabled) { | |
return; | |
} | |
if (options.disableScroll) event.preventDefault(); | |
var touches = event.touches[0]; | |
// measure change in y and x | |
delta = { | |
y: touches.pageY - start.y, | |
x: touches.pageX - start.x | |
}; | |
// determine if scrolling test has run - one time test | |
if ( typeof isScrolling == 'undefined') { | |
isScrolling = !!( isScrolling || Math.abs(delta.y) < Math.abs(delta.x) ); | |
} | |
// if user is not trying to scroll vertically | |
if (!isScrolling) { | |
// prevent native scrolling | |
event.preventDefault(); | |
// stop slideshow | |
stop(); | |
// increase resistance if first or last slide | |
if (options.continuous) { // we don't add resistance at the end | |
translate(circle(index-1), delta.y + slidePos[circle(index-1)], 0); | |
translate(index, delta.y + slidePos[index], 0); | |
translate(circle(index+1), delta.y + slidePos[circle(index+1)], 0); | |
} else { | |
delta.y = | |
delta.y / | |
( (!index && delta.y > 0 || // if first slide and sliding top | |
index == slides.length - 1 && // or if last slide and sliding bottom | |
delta.y < 0 // and if sliding at all | |
) ? | |
( Math.abs(delta.y) / height + 1 ) // determine resistance level | |
: 1 ); // no resistance if false | |
// translate 1:1 | |
translate(index-1, delta.y + slidePos[index-1], 0); | |
translate(index, delta.y + slidePos[index], 0); | |
translate(index+1, delta.y + slidePos[index+1], 0); | |
} | |
} | |
}, | |
end: function(event) { | |
// measure duration | |
var duration = +new Date() - start.time; | |
// determine if slide attempt triggers next/prev slide | |
var isValidSlide = | |
Number(duration) < 250 && // if slide duration is less than 250ms | |
Math.abs(delta.y) > 20 || // and if slide amt is greater than 20px | |
Math.abs(delta.y) > height/2; // or if slide amt is greater than half the height | |
// determine if slide attempt is past start and end | |
var isPastBounds = (!index && delta.y > 0) || // if first slide and slide amt is greater than 0 | |
(index == slides.length - 1 && delta.y < 0); // or if last slide and slide amt is less than 0 | |
if (options.continuous) isPastBounds = false; | |
// determine direction of swipe (true:bottom, false:top) | |
var direction = delta.y < 0; | |
// if not scrolling vertically | |
if (!isScrolling) { | |
if (isValidSlide && !isPastBounds) { | |
if (direction) { | |
if (options.continuous) { // we need to get the next in this direction in place | |
move(circle(index-1), -height, 0); | |
move(circle(index+2), height, 0); | |
} else { | |
move(index-1, -height, 0); | |
} | |
move(index, slidePos[index]-height, speed); | |
move(circle(index+1), slidePos[circle(index+1)]-height, speed); | |
index = circle(index+1); | |
} else { | |
if (options.continuous) { // we need to get the next in this direction in place | |
move(circle(index+1), height, 0); | |
move(circle(index-2), -height, 0); | |
} else { | |
move(index+1, height, 0); | |
} | |
move(index, slidePos[index]+height, speed); | |
move(circle(index-1), slidePos[circle(index-1)]+height, speed); | |
index = circle(index-1); | |
} | |
options.callback && options.callback(index, slides[index]); | |
} else { | |
if (options.continuous) { | |
move(circle(index-1), -height, speed); | |
move(index, 0, speed); | |
move(circle(index+1), height, speed); | |
} else { | |
move(index-1, -height, speed); | |
move(index, 0, speed); | |
move(index+1, height, speed); | |
} | |
} | |
} | |
// kill touchmove and touchend event listeners until touchstart called again | |
if(browser.touch) { | |
element.removeEventListener('touchmove', events, false); | |
element.removeEventListener('touchend', events, false); | |
} else { | |
element.removeEventListener('mousemove', events, false); | |
element.removeEventListener('mouseup', events, false); | |
document.removeEventListener('mouseup', events, false); | |
} | |
}, | |
transitionEnd: function(event) { | |
if (parseInt(event.target.getAttribute('data-index'), 10) == index) { | |
if (delay) begin(); | |
options.transitionEnd && options.transitionEnd.call(event, index, slides[index]); | |
} | |
} | |
}; | |
// Public API | |
this.update = function() { | |
setTimeout(setup); | |
}; | |
this.setup = function() { | |
setup(); | |
}; | |
this.enableSlide = function(shouldEnable) { | |
if (arguments.length) { | |
this.slideIsDisabled = !shouldEnable; | |
} | |
return !this.slideIsDisabled; | |
}, | |
this.slide = function(to, speed) { | |
// cancel slideshow | |
stop(); | |
slide(to, speed); | |
}; | |
this.prev = this.previous = function() { | |
// cancel slideshow | |
stop(); | |
prev(); | |
}; | |
this.next = function() { | |
// cancel slideshow | |
stop(); | |
next(); | |
}; | |
this.stop = function() { | |
// cancel slideshow | |
stop(); | |
}; | |
this.start = function() { | |
begin(); | |
}; | |
this.currentIndex = function() { | |
// return current index position | |
return index; | |
}; | |
this.slidesCount = function() { | |
// return total number of slides | |
return length; | |
}; | |
this.kill = function() { | |
// cancel slideshow | |
stop(); | |
// reset element | |
element.style.height = ''; | |
element.style.top = ''; | |
// reset slides | |
var pos = slides.length; | |
while(pos--) { | |
var slide = slides[pos]; | |
slide.style.height = ''; | |
slide.style.top = ''; | |
if (browser.transitions) translate(pos, 0, 0); | |
} | |
// removed event listeners | |
if (browser.addEventListener) { | |
// remove current event listeners | |
element.removeEventListener('touchstart', events, false); | |
element.removeEventListener('webkitTransitionEnd', events, false); | |
element.removeEventListener('msTransitionEnd', events, false); | |
element.removeEventListener('oTransitionEnd', events, false); | |
element.removeEventListener('otransitionend', events, false); | |
element.removeEventListener('transitionend', events, false); | |
window.removeEventListener('resize', events, false); | |
} | |
else { | |
window.onresize = null; | |
} | |
}; | |
this.load = function() { | |
// trigger setup | |
setup(); | |
// start auto slideshow if applicable | |
if (delay) begin(); | |
// add event listeners | |
if (browser.addEventListener) { | |
// set touchstart event on element | |
if (browser.touch) { | |
element.addEventListener('touchstart', events, false); | |
} else { | |
element.addEventListener('mousedown', events, false); | |
} | |
if (browser.transitions) { | |
element.addEventListener('webkitTransitionEnd', events, false); | |
element.addEventListener('msTransitionEnd', events, false); | |
element.addEventListener('oTransitionEnd', events, false); | |
element.addEventListener('otransitionend', events, false); | |
element.addEventListener('transitionend', events, false); | |
} | |
// set resize event on window | |
window.addEventListener('resize', events, false); | |
} else { | |
window.onresize = function () { setup(); }; // to play nice with old IE | |
} | |
}; | |
} | |
}); | |
})(ionic); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment