strong-arc
Version:
A visual suite for the StrongLoop API Platform
215 lines (184 loc) • 10 kB
JavaScript
/*
jQuery UI Slider plugin wrapper
*/
angular.module('ui.slider', []).value('uiSliderConfig',{}).directive('uiSlider', ['uiSliderConfig', '$timeout', function(uiSliderConfig, $timeout) {
uiSliderConfig = uiSliderConfig || {};
return {
require: 'ngModel',
compile: function() {
var preLink = function (scope, elm, attrs, ngModel) {
function parseNumber(n, decimals) {
return (decimals) ? parseFloat(n) : parseInt(n, 10);
}
var directiveOptions = angular.copy(scope.$eval(attrs.uiSlider));
var options = angular.extend(directiveOptions || {}, uiSliderConfig);
// Object holding range values
var prevRangeValues = {
min: null,
max: null
};
// convenience properties
var properties = ['min', 'max', 'step', 'lowerBound', 'upperBound'];
var useDecimals = (!angular.isUndefined(attrs.useDecimals)) ? true : false;
var init = function() {
// When ngModel is assigned an array of values then range is expected to be true.
// Warn user and change range to true else an error occurs when trying to drag handle
if (angular.isArray(ngModel.$viewValue) && options.range !== true) {
console.warn('Change your range option of ui-slider. When assigning ngModel an array of values then the range option should be set to true.');
options.range = true;
}
// Ensure the convenience properties are passed as options if they're defined
// This avoids init ordering issues where the slider's initial state (eg handle
// position) is calculated using widget defaults
// Note the properties take precedence over any duplicates in options
angular.forEach(properties, function(property) {
if (angular.isDefined(attrs[property])) {
options[property] = parseNumber(attrs[property], useDecimals);
}
});
elm.slider(options);
init = angular.noop;
};
// Find out if decimals are to be used for slider
angular.forEach(properties, function(property) {
// support {{}} and watch for updates
attrs.$observe(property, function(newVal) {
if (!!newVal) {
init();
options[property] = parseNumber(newVal, useDecimals);
elm.slider('option', property, parseNumber(newVal, useDecimals));
ngModel.$render();
}
});
});
attrs.$observe('disabled', function(newVal) {
init();
elm.slider('option', 'disabled', !!newVal);
});
// Watch ui-slider (byVal) for changes and update
scope.$watch(attrs.uiSlider, function(newVal) {
init();
if(newVal !== undefined) {
elm.slider('option', newVal);
}
}, true);
// Late-bind to prevent compiler clobbering
$timeout(init, 0, true);
// Update model value from slider
elm.bind('slide', function(event, ui) {
var valuesChanged;
if (ui.values) {
var boundedValues = ui.values.slice();
if (options.lowerBound && boundedValues[0] < options.lowerBound) {
boundedValues[0] = Math.max(boundedValues[0], options.lowerBound);
}
if (options.upperBound && boundedValues[1] > options.upperBound) {
boundedValues[1] = Math.min(boundedValues[1], options.upperBound);
}
if (boundedValues[0] !== ui.values[0] || boundedValues[1] !== ui.values[1]) {
valuesChanged = true;
ui.values = boundedValues;
}
} else {
var boundedValue = ui.value;
if (options.lowerBound && boundedValue < options.lowerBound) {
boundedValue = Math.max(boundedValue, options.lowerBound);
}
if (options.upperBound && boundedValue > options.upperBound) {
boundedValue = Math.min(boundedValue, options.upperBound);
}
if (boundedValue !== ui.value) {
valuesChanged = true;
ui.value = boundedValue;
}
}
ngModel.$setViewValue(ui.values || ui.value);
scope.$apply();
if (valuesChanged) {
setTimeout(function() {
elm.slider('value', ui.values || ui.value);
}, 0);
return false;
}
});
// Update slider from model value
ngModel.$render = function() {
init();
var method = options.range === true ? 'values' : 'value';
if (options.range !== true && isNaN(ngModel.$viewValue) && !(ngModel.$viewValue instanceof Array)) {
ngModel.$viewValue = 0;
}
else if (options.range && !angular.isDefined(ngModel.$viewValue)) {
ngModel.$viewValue = [0,0];
}
// Do some sanity check of range values
if (options.range === true) {
// previously, the model was a string b/c it was in a text input, need to convert to a array.
// make sure input exists, comma exists once, and it is a string.
if (ngModel.$viewValue && angular.isString(ngModel.$viewValue) && (ngModel.$viewValue.match(/,/g) || []).length === 1) {
// transform string model into array.
var valueArr = ngModel.$viewValue.split(',');
ngModel.$viewValue = [Number(valueArr[0]), Number(valueArr[1])];
}
// Check outer bounds for min and max values
if (angular.isDefined(options.min) && options.min > ngModel.$viewValue[0]) {
ngModel.$viewValue[0] = options.min;
}
if (angular.isDefined(options.max) && options.max < ngModel.$viewValue[1]) {
ngModel.$viewValue[1] = options.max;
}
// Check min and max range values
if (ngModel.$viewValue[0] > ngModel.$viewValue[1]) {
// Min value should be less to equal to max value
if (prevRangeValues.min >= ngModel.$viewValue[1]) {
ngModel.$viewValue[1] = prevRangeValues.min;
}
// Max value should be less to equal to min value
if (prevRangeValues.max <= ngModel.$viewValue[0]) {
ngModel.$viewValue[0] = prevRangeValues.max;
}
}
// Store values for later user
prevRangeValues.min = ngModel.$viewValue[0];
prevRangeValues.max = ngModel.$viewValue[1];
}
elm.slider(method, ngModel.$viewValue);
};
scope.$watch(attrs.ngModel, function() {
if (options.range === true) {
ngModel.$render();
}
}, true);
function destroy() {
if (elm.hasClass('ui-slider')) {
elm.slider('destroy');
}
}
scope.$on("$destroy", destroy);
elm.one('$destroy', destroy);
};
var postLink = function (scope, element, attrs, ngModel) {
// Add tick marks if 'tick' and 'step' attributes have been setted on element.
// Support horizontal slider bar so far. 'tick' and 'step' attributes are required.
var options = angular.extend({}, scope.$eval(attrs.uiSlider));
var properties = ['min', 'max', 'step', 'tick'];
angular.forEach(properties, function(property) {
if (angular.isDefined(attrs[property])) {
options[property] = attrs[property];
}
});
if (angular.isDefined(options['tick']) && angular.isDefined(options['step'])) {
var total = parseInt( (parseInt(options['max']) - parseInt(options['min'])) /parseInt(options['step']));
for (var i = total; i >= 0; i--) {
var left = ((i / total) * 100) + '%';
$("<div/>").addClass("ui-slider-tick").appendTo(element).css({left: left});
};
}
}
return {
pre: preLink,
post: postLink
};
}
};
}]);