Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
Use hammer for panning
Browse files Browse the repository at this point in the history
  • Loading branch information
ajoslin committed Sep 12, 2014
1 parent b6d97b2 commit f3f4c55
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 58 deletions.
2 changes: 1 addition & 1 deletion src/components/slider/demo1/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h3>
<div flex="10" layout layout-align="center center">
<span>R</span>
</div>
<material-slider flex ng-model="color.red" min="0" max="255">
<material-slider flex min="0" max="255" ng-model="color.red">
</material-slider>
<div flex="20" layout layout-align="center center">
<input type="number" ng-model="color.red">
Expand Down
165 changes: 110 additions & 55 deletions src/components/slider/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ angular.module('material.components.slider', [
'material.animations'
])
.directive('materialSlider', [
'$materialEffects',
'$timeout',
'$$rAF',
'$window',
SliderDirective
]);

Expand Down Expand Up @@ -47,16 +43,25 @@ angular.module('material.components.slider', [
* @param {number=} min The minimum value the user is allowed to pick. Default 0.
* @param {number=} max The maximum value the user is allowed to pick. Default 100.
*/
function SliderDirective($materialEffects, $timeout, $$rAF, $window) {
function SliderDirective() {
var hasTouch = !!('ontouchend' in document);
var POINTERDOWN_EVENT = hasTouch ? 'touchstart' : 'mousedown';
var POINTERUP_EVENT = hasTouch ? 'touchend touchcancel' : 'mouseup mouseleave';
var POINTERMOVE_EVENT = hasTouch ? 'touchmove' : 'mousemove';

return {
require: '?ngModel',
scope: {
},
scope: {},
require: ['?ngModel', 'materialSlider'],
controller: [
'$scope',
'$element',
'$attrs',
'$$rAF',
'$timeout',
'$window',
'$materialEffects',
SliderController
],
template:
'<div class="slider-track-container">' +
'<div class="slider-track">' +
Expand All @@ -82,8 +87,32 @@ function SliderDirective($materialEffects, $timeout, $$rAF, $window) {
'</div>',
link: postLink
};

function postLink(scope, element, attr, ctrls) {
var ngModelCtrl = ctrls[0] || {
// Mock ngModelController if it doesn't exist to give us
// the minimum functionality needed
$setViewValue: function(val) {
this.$viewValue = val;
this.$viewChangeListeners.forEach(function(cb) { cb(); });
},
$parsers: [],
$formatters: [],
$viewChangeListeners: []
};

function postLink(scope, element, attr, ngModelCtrl) {
var sliderCtrl = ctrls[1];
sliderCtrl.init(ngModelCtrl);
}
}

/**
* We use a controller for all the logic so that we can expose a few
* things to unit tests
*/
function SliderController(scope, element, attr, $$rAF, $timeout, $window, $materialEffects) {

this.init = function init(ngModelCtrl) {
var thumb = angular.element(element[0].querySelector('.slider-thumb'));
var thumbContainer = thumb.parent();
var trackContainer = angular.element(element[0].querySelector('.slider-track-container'));
Expand All @@ -95,22 +124,30 @@ function SliderDirective($materialEffects, $timeout, $$rAF, $window) {
attr.max ? attr.$observe('max', updateMax) : updateMax(100);
attr.step ? attr.$observe('step', updateStep) : updateStep(1);

element.attr('tabIndex', 0);
element.on('keydown', keydownListener);

var hammertime = new Hammer(element[0], {
recognizers: [
[Hammer.Pan, { direction: Hammer.DIRECTION_HORIZONTAL }]
]
});
hammertime.on('hammer.input', onInput);
hammertime.on('panstart', onPanStart);
hammertime.on('pan', onPan);

// On resize, recalculate the slider's dimensions and re-render
var onWindowResize = $$rAF.debounce(function() {
refreshSliderDimensions();
ngModelRender();
});
angular.element($window).on('resize', onWindowResize);

scope.$on('$destroy', function() {
angular.element($window).off('resize', onWindowResize);
hammertime.destroy();
});

element.attr('tabIndex', 0);
element.on('keydown', keydownListener);
element.on(POINTERDOWN_EVENT, onPointerDown);
element.on(POINTERMOVE_EVENT, onPointerMove);
element.on(POINTERUP_EVENT, onPointerUp);

ngModelCtrl.$render = ngModelRender;
ngModelCtrl.$viewChangeListeners.push(ngModelRender);
ngModelCtrl.$formatters.push(minMaxValidator);
Expand Down Expand Up @@ -148,54 +185,30 @@ function SliderDirective($materialEffects, $timeout, $$rAF, $window) {
}

/**
* Slide listeners
* left/right arrow listener
*/
var pointerState = {};
function onPointerDown(ev) {
if (element[0].hasAttribute('disabled')) return;
if (pointerState.down) return;

pointerState.down = true;
element.addClass('active');
element[0].focus();
function keydownListener(ev) {
// Support jQuery events
ev = (ev.originalEvent || ev);

refreshSliderDimensions();
doEventSliderMovement(ev);
}
function onPointerMove(ev) {
if (!pointerState.down) return;
var stepAmount = step;

if (!pointerState.moving) {
pointerState.moving = true;
element.addClass('panning');
if (ev.metaKey || ev.ctrlKey || ev.altKey) {
// When pressing ctrl/meta/alt, go up step * 5. Or, if step * 5
// is going to be the whole slider, max out to half the slider.
stepAmount = Math.min(stepAmount * 5, (max - min) / 2);
}

ev.preventDefault();
doEventSliderMovement(ev);
}
function onPointerUp(ev) {
pointerState = {};
element.removeClass('panning active');
}
function doEventSliderMovement(ev) {
// Support jQuery events
ev = ev.originalEvent || ev;
var x = ev.touches ? ev.touches[0].pageX : ev.pageX;

var percent = (x - sliderDimensions.left) / (sliderDimensions.width);
scope.$evalAsync(function() { setModelValue(min + percent * (max - min)); });
}

/**
* left/right arrow listener
*/
function keydownListener(ev) {
if (ev.which === Constant.KEY_CODE.LEFT_ARROW) {
ev.preventDefault();
scope.$evalAsync(function() { setModelValue(ngModelCtrl.$viewValue - step); });
scope.$evalAsync(function() {
setModelValue(ngModelCtrl.$viewValue - stepAmount);
});
} else if (ev.which === Constant.KEY_CODE.RIGHT_ARROW) {
ev.preventDefault();
scope.$evalAsync(function() { setModelValue(ngModelCtrl.$viewValue + step); });
scope.$evalAsync(function() {
setModelValue(ngModelCtrl.$viewValue + stepAmount);
});
}
}

Expand Down Expand Up @@ -234,5 +247,47 @@ function SliderDirective($materialEffects, $timeout, $$rAF, $window) {
element.toggleClass('slider-min', percent === 0);
}

}

/**
* Slide listeners
*/
var isSliding = false;
function onInput(ev) {
if (!isSliding && ev.eventType === Hammer.INPUT_START &&
!element[0].hasAttribute('disabled')) {

isSliding = true;
element.addClass('active');
element[0].focus();
refreshSliderDimensions();
doSlide(ev.center.x);

} else if (isSliding && ev.eventType === Hammer.INPUT_END) {
isSliding = false;
element.removeClass('panning active');
}
}
function onPanStart() {
if (!isSliding) return;
element.addClass('panning');
}
function onPan(ev) {
if (!isSliding) return;
doSlide(ev.center.x);
ev.preventDefault();
}

/**
* Expose for testing
*/
this._onInput = onInput;
this._onPanStart = onPanStart;
this._onPan = onPan;

function doSlide(x) {
var percent = (x - sliderDimensions.left) / (sliderDimensions.width);
scope.$evalAsync(function() { setModelValue(min + percent * (max - min)); });
}

};
}
13 changes: 11 additions & 2 deletions src/components/slider/slider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ describe('material-slider', function() {

beforeEach(module('material.components.slider'));

it('should work', function() {
});
it('should set set on press', inject(function($compile, $rootScope, $timeout) {
var slider = $compile('<material-slider ng-model="value" min="0" max="100">')($rootScope);
$rootScope.$apply('value = 50');
var sliderCtrl = slider.controller('materialSlider');

sliderCtrl._onInput({
eventType: Hammer.INPUT_START,
center: { x: 0 }
});
$timeout.flush();
}));


});

0 comments on commit f3f4c55

Please sign in to comment.