'use strict';
/**
* @name keta.filters.Unit
* @author Marco Lehmann <marco.lehmann@kiwigrid.com>
* @copyright Kiwigrid GmbH 2014-2015
* @module keta.filters.Unit
* @description
* <p>
* A filter using SI prefixes to format numeric values.
* </p>
* <p>
* The filter takes a couple of parameters to configure it. <code>unit</code> defines the unit
* as string to append to formatted value (e.g. <code>'W'</code>, defaults to empty string).
* <code>precision</code> defines the number of digits to appear after the decimal point as integer
* (e.g. <code>2</code>, defaults to <code>0</code>). <code>precisionRanges</code> defines used
* precision in a more flexible way by defining an array of precisions with <code>min</code> (included)
* and/or <code>max</code> (excluded) value. <code>precisionExcludeIntegers</code> defines if integers
* should be excluded from precision. <code>precisionExclude</code> defines concrete values which are excluded
* from precision. <code>isBytes</code> is a boolean flag tom specify if the given number is bytes and
* therefor 1024-based (defaults to <code>false</code>). <code>separate</code> is a boolean flag
* (defaults to <code>false</code>) which defines whether to return a single string or an object with
* separated values <code>numberFormatted</code> (String), <code>numberRaw</code> (Number) and
* <code>unit</code> (String).
* </p>
* <p>
* If <code>precisionRanges</code> is set to:
* </p>
* <pre>[
* {max: 1000, precision: 0},
* {min: 1000, precision: 1}
* ]</pre>
* <p>
* If <code>precisionExclude</code> is set to:
* </p>
* <pre>[0.123456, 100.123456]</pre>
* <p>
* values that are 0.123456 or 100.123456 wouldn't be cutted by precision.
* </p>
* <p>
* numeric values which are less than 1000 are formatted with a precision of 0, as numeric values
* equal or greater than 1000 are formatted with a precision of 1.
* </p>
* <p>
* If <code>separate</code> is set to <code>true</code> the filter returns an object in the
* following manner if for instance German is the current locale:
* </p>
* <pre>{
* numberFormatted: '1,546',
* numberRaw: 1.546,
* unit: 'kW'
* }</pre>
* @example
* {{ 1234.56 | ketaUnit:{unit: 'W', precision: 1, isBytes: false} }}
*
* Number: {{ (1234.56 | ketaUnit:{unit: 'W', precision: 1, isBytes: false, separate:true}).numberFormatted }}
* Unit: {{ (1234.56 | ketaUnit:{unit: 'W', precision: 1, isBytes: false, separate:true}).unit }}
* @example
* angular.module('exampleApp', ['keta.filters.Unit'])
* .controller('ExampleController', function($scope) {
*
* // use unit filter to return formatted number value
* // $scope.value equals string '1.2 kW'
* $scope.value = $filter('ketaUnit')(1234.56, {
* unit: 'W',
* precision: 1,
* isBytes: false
* });
*
* // use unit filter for integers that shouldn't be cutted by precision
* // $scope.valuePrecisionIntegersExcluded equals string '123 W'
* $scope.valuePrecisionIntegersExcluded = $filter('ketaUnit')(123, {
* unit: 'W',
* precision: 2,
* precisionExcludeIntegers: true
* });
*
* // use unit filter for values that shouldn't be cutted by precision
* // $scope.valuePrecisionExcluded equals string '0.123456 W'
* $scope.valuePrecisionExcluded = $filter('ketaUnit')(0.123456, {
* unit: 'W',
* precision: 2,
* precisionExclude: [0.123456]
* });
*
* // use unit filter to return object for number formatting
* // $scope.valueSeparated equals object {numberFormatted: '1.2', numberRaw: 1.2, unit: 'kW'}
* // as numberFormatted is locale-aware, numberRaw remains a real number to calculate with
* // e.g. for German numberFormatted would be formatted to '1,2' and numberRaw would still be 1.2
* $scope.valueSeparated = $filter('ketaUnit')(1234.56, {
* unit: 'W',
* precision: 1,
* isBytes: false,
* separate: true
* });
*
* // use unit filter with precision ranges
* // for the example below all values which are less than 1000 are formatted with a precision of 0
* // and all values equal or greater than 1000 are formatted with a precision of 1
* $scope.valueRanges = $filter('ketaUnit')(1234.56, {
* unit: 'W',
* precision: 1,
* precisionRanges: [
* {max: 1000, precision: 0},
* {min: 1000, precision: 1}
* ],
* isBytes: false
* });
*
* });
*/
angular.module('keta.filters.Unit',
[
'keta.services.Tag'
])
.filter('ketaUnit', function($filter, ketaTagConstants) {
var unitFilter = function unitFilter(input, configuration) {
if (!angular.isNumber(input)) {
return input;
}
var precision = 0,
precisionRanges = [],
precisionExcludeIntegers = false,
precisionExclude = [],
unit = '',
isBytes = false,
separate = false,
separated = {
numberFormatted: null,
numberRaw: null,
unit: null
};
if (angular.isDefined(configuration)) {
// general precision (defaults to 0)
precision =
angular.isNumber(configuration.precision) ?
configuration.precision : precision;
// precision ranges (defaults to [])
precisionRanges =
angular.isArray(configuration.precisionRanges) ?
configuration.precisionRanges : precisionRanges;
// flag if decimal places shouldn't be forced by precision
precisionExcludeIntegers = angular.isDefined(configuration.precisionExcludeIntegers) ?
configuration.precisionExcludeIntegers : precisionExcludeIntegers;
// precision excluded values
precisionExclude =
angular.isArray(configuration.precisionExclude) ?
configuration.precisionExclude : precisionExclude;
// unit to use (defaults to '')
unit =
angular.isDefined(configuration.unit) ?
configuration.unit : unit;
// flag if value represents bytes (defaults to false)
isBytes =
angular.isDefined(configuration.isBytes) ?
configuration.isBytes : isBytes;
// flag if result should be returned separate (defaults to false)
separate =
angular.isDefined(configuration.separate) ?
configuration.separate : separate;
}
var excludeFromPrecision = precisionExcludeIntegers;
// checking if input is an excluded value
if (!excludeFromPrecision) {
excludeFromPrecision = precisionExclude.indexOf(input) !== -1;
}
// reset precision if precision range matches
angular.forEach(precisionRanges, function(range) {
var matching = true;
if (angular.isDefined(range.min) && input < range.min) {
matching = false;
}
if (angular.isDefined(range.max) && input >= range.max) {
matching = false;
}
if (!angular.isDefined(range.min) && !angular.isDefined(range.max)) {
matching = false;
}
if (matching && angular.isNumber(range.precision)) {
precision = range.precision;
}
});
if (input === 0 && excludeFromPrecision) {
precision = 0;
}
var sizes = isBytes ? ['Bytes', 'KB', 'MB', 'GB', 'TB'] : ['', 'k', 'M', 'G', 'T'];
// directly return currencies and distances
if (unit === ketaTagConstants.UNIT.EURO ||
unit === ketaTagConstants.UNIT.KILOMETER ||
unit === ketaTagConstants.UNIT.DOLLAR ||
unit === ketaTagConstants.UNIT.POUND) {
if (separate) {
separated.numberFormatted = $filter('number')(input, precision);
separated.numberRaw = input;
separated.unit = unit;
return separated;
}
return $filter('number')(input, precision) + ' ' + unit;
}
var multiplicator = input < 0 ? -1 : 1;
var oneKiloByte = 1024;
var oneKilo = 1000;
var parseBase = 10;
input *= multiplicator;
if (input >= 1) {
var i = parseInt(
Math.floor(Math.log(input) / Math.log(isBytes ? oneKiloByte : oneKilo)),
parseBase
);
var siInput = input / Math.pow(isBytes ? oneKiloByte : oneKilo, i) * multiplicator;
var siInputFixed = excludeFromPrecision ? siInput : Number(siInput.toFixed(precision));
if (siInputFixed >= oneKilo) {
i++;
siInputFixed /= oneKilo;
}
// determine number of decimal places
var inputPieces = String(siInputFixed).split(/\./);
var inputsCurrentPrecision = inputPieces.length > 1 ? inputPieces[1].length : 0;
separated.numberFormatted = excludeFromPrecision ?
$filter('number')(siInputFixed, inputsCurrentPrecision) :
$filter('number')(siInputFixed, precision);
separated.numberRaw = siInputFixed;
separated.unit = sizes[i] + unit;
input = separated.numberFormatted +
(sizes[i] !== '' ? ' ' + sizes[i] : '');
} else {
separated.numberFormatted = $filter('number')(input, precision);
separated.numberRaw = Number(input.toFixed(precision));
separated.unit = unit;
input = separated.numberFormatted;
}
if (!isBytes && unit !== '') {
input += input.indexOf(' ') === -1 ? ' ' + unit : unit;
}
if (separate) {
return separated;
}
return input;
};
unitFilter.$stateful = true;
return unitFilter;
});