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

Commit

Permalink
fix(slider): discrete sliders now support live dragging between discr…
Browse files Browse the repository at this point in the history
…ete values and snap-to animate to closest discrete value.
  • Loading branch information
ThomasBurleson committed Sep 28, 2014
1 parent fd7697d commit 3d3eb71
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 19 deletions.
81 changes: 74 additions & 7 deletions src/components/slider/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ function SliderDirective() {
'$element',
'$attrs',
'$$rAF',
'$timeout',
'$window',
'$materialEffects',
'$aria',
Expand Down Expand Up @@ -99,7 +98,7 @@ function SliderDirective() {
* 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, $aria) {
function SliderController(scope, element, attr, $$rAF, $window, $materialEffects, $aria) {

this.init = function init(ngModelCtrl) {
var thumb = angular.element(element[0].querySelector('.slider-thumb'));
Expand Down Expand Up @@ -136,6 +135,7 @@ function SliderController(scope, element, attr, $$rAF, $timeout, $window, $mater
hammertime.on('hammer.input', onInput);
hammertime.on('panstart', onPanStart);
hammertime.on('pan', onPan);
hammertime.on('panend', onPanEnd);

// On resize, recalculate the slider's dimensions and re-render
var updateAll = $$rAF.debounce(function() {
Expand Down Expand Up @@ -282,18 +282,25 @@ function SliderController(scope, element, attr, $$rAF, $timeout, $window, $mater
* Slide listeners
*/
var isSliding = false;
var isDiscrete = angular.isDefined(attr.discrete);

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);

onPan(ev);

} else if (isSliding && ev.eventType === Hammer.INPUT_END) {

if ( isDiscrete ) onPanEnd(ev);
isSliding = false;

element.removeClass('panning active');
}
}
Expand All @@ -303,8 +310,33 @@ function SliderController(scope, element, attr, $$rAF, $timeout, $window, $mater
}
function onPan(ev) {
if (!isSliding) return;
doSlide(ev.center.x);

// While panning discrete, update only the
// visual positioning but not the model value.

if ( isDiscrete ) doPan( ev.center.x );
else doSlide( ev.center.x );

ev.preventDefault();
ev.srcEvent.stopPropagation();
}

function onPanEnd(ev) {
if ( isDiscrete ) {
// Convert exact to closest discrete value.
// Slide animate the thumb... and then update the model value.

var exactVal = percentToValue( positionToPercent( ev.center.x ));
var closestVal = minMaxValidator( stepValidator(exactVal) );

setSliderPercent( valueToPercent(closestVal));
$$rAF(function(){

This comment has been minimized.

Copy link
@ajoslin

ajoslin Sep 29, 2014

Contributor

What's the purpose of this rAF?

This comment has been minimized.

Copy link
@ThomasBurleson

ThomasBurleson Sep 29, 2014

Author Contributor

We want to update the model value ONLY when the slider animation is done

setModelValue( closestVal );
});

ev.preventDefault();
ev.srcEvent.stopPropagation();
}
}

/**
Expand All @@ -314,9 +346,44 @@ function SliderController(scope, element, attr, $$rAF, $timeout, $window, $mater
this._onPanStart = onPanStart;
this._onPan = onPan;

function doSlide(x) {
var percent = (x - sliderDimensions.left) / (sliderDimensions.width);
scope.$evalAsync(function() { setModelValue(min + percent * (max - min)); });
/**
* Slide the UI by changing the model value
* @param x
*/
function doSlide( x ) {
scope.$evalAsync( function() {
setModelValue( percentToValue( positionToPercent(x) ));
});
}

/**
* Slide the UI without changing the model (while dragging/panning)
* @param x
*/
function doPan( x ) {

This comment has been minimized.

Copy link
@ajoslin

ajoslin Sep 29, 2014

Contributor

How about we rename this to adjustThumbPosition(x)? Doesn't imply a model change.

This comment has been minimized.

Copy link
@ThomasBurleson

ThomasBurleson Sep 29, 2014

Author Contributor

I like both ideas. I will make those.

setSliderPercent( positionToPercent(x) );
}

/**
* Convert horizontal position on slider to percentage value of offset from beginning...
* @param x
* @returns {number}
*/
function positionToPercent( x ) {
return (x - sliderDimensions.left) / (sliderDimensions.width);
}

/**
* Convert percentage offset on slide to equivalent model value
* @param percent
* @returns {*}
*/
function percentToValue( percent ) {
return (min + percent * (max - min));
}

function valueToPercent( val ) {
return (val - min)/(max - min);
}

};
Expand Down
26 changes: 14 additions & 12 deletions src/components/slider/slider.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@

describe('material-slider', function() {

function simulateEventAt( centerX, eventType ) {
return {
eventType: eventType,
center: { x: centerX },
preventDefault: angular.noop,
srcEvent : {
stopPropagation : angular.noop
}
};
}

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

it('should set model on press', inject(function($compile, $rootScope, $timeout) {
Expand All @@ -14,25 +25,16 @@ describe('material-slider', function() {
right: 0
});

sliderCtrl._onInput({
eventType: Hammer.INPUT_START,
center: { x: 30 }
});
sliderCtrl._onInput( simulateEventAt( 30, Hammer.INPUT_START ));
$timeout.flush();
expect($rootScope.value).toBe(30);

//When going past max, it should clamp to max
sliderCtrl._onPan({
center: { x: 500 },
preventDefault: angular.noop
});
sliderCtrl._onPan( simulateEventAt( 500 ));
$timeout.flush();
expect($rootScope.value).toBe(100);

sliderCtrl._onPan({
center: { x: 50 },
preventDefault: angular.noop
});
sliderCtrl._onPan( simulateEventAt( 50 ));
$timeout.flush();
expect($rootScope.value).toBe(50);
}));
Expand Down

0 comments on commit 3d3eb71

Please sign in to comment.