home *** CD-ROM | disk | FTP | other *** search
- // Copyright 2008 Google Inc.
- // All Rights Reserved.
-
- /**
- * @fileoverview DynamicComponent-specific unit conversion support functions.
- * The data structures and methods here are used in both the configure and
- * manager dialogs to convert units.
- *
- * This file is used inside SketchUp as a local javascript library. On SketchUp
- * for the Mac, it runs inside Safari and on SketchUp for PC it runs inside IE.
- * These are the only browsers this is ever used in.
- */
-
- // Declare su namespace.
- var su = window.su;
-
- // Declare skp namespace.
- var skp = window.skp;
-
- // Declare mgr namespace.
- var mgr = window.mgr;
-
- // Define a "conv" object where we can hang our functionality.
- var conv = {};
-
- /**
- * Default number of decimal places to show at format time.
- * @type {number}
- */
- conv.DEFAULT_FORMAT_DECIMAL_PLACES = 3;
-
- /**
- * Array of objects that describe the supported units.
- * @type {array}
- */
- conv.units = [
- {
- name: 'DEFAULT',
- label: 'User\'s default template units',
- group: 'LENGTH',
- configOnly: true
- },
- {
- name: 'INTEGER',
- label: 'Whole Number, no units',
- group: 'INTEGER',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'FLOAT',
- label: 'Decimal Number, no units',
- group: 'FLOAT'
- },
- {
- name: 'PERCENT',
- label: 'Percentage',
- group: 'FLOAT',
- test: '^[\\d\\.,\\-\\s]+\\%[^\\w]*$',
- template: '{value}%',
- conversion: 100,
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'BOOLEAN',
- label: 'True/False',
- group: 'BOOLEAN',
- test: '^(true|false)$',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'STRING',
- label: 'Arbitrary Text, no units',
- group: 'STRING'
- },
- {
- name: 'INCHES',
- label: 'Inches',
- template: '{value}"',
- group: 'LENGTH',
- test: '^\d*\s*[^\\\']\s*\\d*\\s*\\"'
- },
- {
- name: 'FEET',
- label: 'Decimal Feet',
- template: "{value}'",
- group: 'LENGTH',
- conversion: 0.0833333333333,
- test: '^[\\d\\.,\\-\\s]+\\\'[\\d\\.,\\-\\s\\\\/"]*$',
- configOnly: true,
- defaultFormulaUnit: 'INCHES'
- },
- {
- name: 'MILLIMETERS',
- label: 'Millimeters',
- template: '{value} mm',
- group: 'LENGTH',
- conversion: 25.4,
- test: '^[\\d\\.,\\-\\s]+mm[^\\w]*$',
- configOnly: true,
- defaultFormulaUnit: 'CENTIMETERS'
- },
- {
- name: 'CENTIMETERS',
- label: 'Centimeters',
- template: '{value} cm',
- conversion: 2.54,
- group: 'LENGTH',
- test: '^[\\d\\.,\\-\\s]+cm[^\\w]*$'
- },
- {
- name: 'METERS',
- label: 'Meters',
- template: '{value} m',
- group: 'LENGTH',
- conversion: .0254,
- test: '^[\\d\\.,\\-\\s]+m[^\\w]*$',
- configOnly: true,
- defaultFormulaUnit: 'CENTIMETERS'
- },
- {
- name: 'DEGREES',
- label: 'Degrees',
- template: '{value}°',
- group: 'ANGLE',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'DOLLARS',
- label: 'Dollars',
- group: 'CURRENCY',
- test: '\\$\\d',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'EUROS',
- label: 'Euros',
- group: 'CURRENCY',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'YEN',
- label: 'Yen',
- group: 'currency',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'POUNDS',
- label: 'Pounds (weight)',
- template: '{value} lbs',
- group: 'WEIGHT',
- test: '\\d\\s*lbs',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- },
- {
- name: 'KILOGRAMS',
- label: 'Kilograms',
- template: '{value} kg',
- group: 'WEIGHT',
- conversion: 0.45359237,
- test: '\\d\\s*kg',
- configOnly: true,
- defaultFormulaUnit: 'FLOAT'
- }
- ];
-
- /**
- * A friendly hash index into our conv.units, to allow us to use easy lookups
- * like conv.unitsHash['CENTIMETERS'].
- * @type {Object}
- */
- conv.unitsHash = {
- 'DEFAULT': conv.units[0],
- 'INTEGER': conv.units[1],
- 'FLOAT': conv.units[2],
- 'PERCENT': conv.units[3],
- 'BOOLEAN': conv.units[4],
- 'STRING': conv.units[5],
- 'INCHES': conv.units[6],
- 'FEET': conv.units[7],
- 'MILLIMETERS': conv.units[8],
- 'CENTIMETERS': conv.units[9],
- 'METERS': conv.units[10],
- 'DEGREES': conv.units[11],
- 'DOLLARS': conv.units[12],
- 'EUROS': conv.units[13],
- 'YEN': conv.units[14],
- 'POUNDS': conv.units[15],
- 'KILOGRAMS': conv.units[16]
- };
-
- /**
- * Array of objects that describe the supported unit groups.
- * @type {Array.<Object>}
- */
- conv.groups = [
- {name: 'STRING', label: 'Text', base: 'STRING'},
- {name: 'LENGTH', label: 'Length', base: 'INCHES'},
- {name: 'INTEGER', label: 'Whole Number', base: 'INTEGER'},
- {name: 'FLOAT', label: 'Decimal Number', base: 'FLOAT'},
- {name: 'ANGLE', label: 'Angle', base: 'DEGREES'},
- {name: 'CURRENCY', label: 'Currency', base: 'DOLLARS'},
- {name: 'WEIGHT', label: 'Weight', base: 'POUNDS'}
- ];
-
- /**
- * A friendly hash index into our conv.groups, to allow us to use easy lookups
- * like conv.groupsHash['LENGTH'].
- * @type {Object}
- */
- conv.groupsHash = {
- 'STRING': conv.groups[0],
- 'LENGTH': conv.groups[1],
- 'INTEGER': conv.groups[2],
- 'FLOAT': conv.groups[3],
- 'ANGLE': conv.groups[4],
- 'CURRENCY': conv.groups[5],
- 'WEIGHT': conv.groups[6]
- };
-
- /**
- * Tests a user-entered string for any matches against out list of unit tests.
- * For example, if a user entered a value like "16'", this would return "FEET".
- * @param {string} string The value to test.
- * @param {boolean} opt_defaultIfConfigOnly If true, then configOnly matches
- * will default down to their approriate defaultFormulaUnit.
- * @return {string} The matching unit type.
- */
- conv.recognizeUnits = function(string, opt_defaultIfConfigOnly) {
- var unit;
- var testExpression;
- var foundUnit;
-
- // Force conversion to string object.
- var stringToTest = string + '';
-
- // Loop across our units to run our tests.
- for (var i = 0; i < conv.units.length; i++) {
- unit = conv.units[i];
- if (su.isValid(unit.test)) {
- testExpression = new RegExp(unit.test, 'i');
- if (testExpression.test(stringToTest)) {
- foundUnit = unit.name
- break;
- }
- }
- }
-
- // Let's convert configOnly units down to the appropriate formulaUnit match
- // if opt_defaultIfConfigOnly is true.
- if (opt_defaultIfConfigOnly == true && su.notEmpty(foundUnit)) {
- foundUnit = su.ifEmpty(conv.unitsHash[foundUnit].defaultFormulaUnit,
- foundUnit)
- }
-
- // If there are no matches, then it counts as a string.
- return su.ifEmpty(foundUnit, 'STRING');
- };
-
- /**
- * Takes a string or number value and converts it into the appropriate base
- * units. For example, a call of parseIntoBase(25.4, 'MILLIMETERS') would
- * return 1.0, since inches are the base unit for millimeters.
- * @param {string|number} value The value to convert.
- * @param {string} units The units name to convert from.
- * @return {string|number} The converted value, or '' if that value is empty.
- */
- conv.toBase = function(value, units) {
- if (su.isEmpty(value)) {
- return '';
- }
-
- var unit = conv.unitsHash[units];
- if (su.notValid(unit) || units == 'STRING') {
- return value;
- }
-
- // See if there is a toBase function. If so, call that.
- var functionName = units.toLowerCase() + 'ToBase';
- if (su.canCall(conv, functionName)) {
- return conv[functionName](value);
- }
-
- var numberValue = conv.toNumber(value);
- if (su.notValid(unit.conversion)) {
- return numberValue;
- }
- return numberValue / unit.conversion;
- };
-
- /**
- * Takes a string or number value and converts it into the passed units from
- * the appropriate base.
- * For example, a call of fromBase(1.0, 'MILLIMETERS') would return 25.4,
- * since inches are the base unit for millimeters.
- * @param {string|number} value The value to convert.
- * @param {string} units The units name to convert into.
- * @return {string|number} The converted value.
- */
- conv.fromBase = function(value, units) {
- var unit = conv.unitsHash[units];
- if (su.notValid(unit) || units == 'STRING') {
- return value;
- }
- var numberValue = conv.toNumber(value);
- if (su.notValid(unit.conversion)) {
- return value;
- }
- return numberValue * unit.conversion;
- };
-
- /**
- * Takes a value and displays it in the passed unit in a pretty format.
- * For example, a call of format(2.53, 'CENTIMETERS', 1, true) would return
- * 2.5cm or 2,5cm (depending on user's decimal delimiter).
- * @param {string|number} value The value to format.
- * @param {string} units The units name to convert to.
- * @param {number} opt_decimalPlaces Number of decimal places to round to.
- * @param {boolean} opt_useTemplate Whether to apply the unit template,
- * defaults to true.
- * @param {string} opt_decimalDelimiter A string containing the decimal
- * delimiter to format with, defaults to '.'.
- * @return {string|number} The converted value.
- */
- conv.format = function(value, units, opt_decimalPlaces, opt_useTemplate,
- opt_decimalDelimiter) {
- var decimalPlaces = su.ifEmpty(opt_decimalPlaces,
- conv.DEFAULT_FORMAT_DECIMAL_PLACES);
- var decimalDelimiter = su.ifEmpty(opt_decimalDelimiter, '.');
-
- // It's bad practice to treat params as vars, so reassign and adjust to
- // handle user template default unit choice.
- var unit = (units == 'DEFAULT') ? skp.units() : units;
-
- var displayValue = value;
-
- if (unit != 'STRING') {
- displayValue = conv.roundDecimalPoints(displayValue, decimalPlaces);
- displayValue = displayValue + '';
- displayValue = displayValue.replace(/\./, decimalDelimiter);
- }
-
- if (opt_useTemplate === false || unit == 'STRING') {
- return displayValue;
- } else {
- // See if there is a formatting function. If so, call that.
- var functionName = 'format' + units.substr(0, 1).toUpperCase() +
- units.substr(1, units.length).toLowerCase();
- if (su.canCall(conv, functionName)) {
- displayValue = conv[functionName](value);
- displayValue = displayValue.replace(/\./, decimalDelimiter);
- return displayValue;
- }
-
- var template = su.ifEmpty(conv.unitsHash[units].template, '{value}');
- displayValue = template.replace(/{value}/gi, displayValue);
- displayValue = displayValue.replace(/\./, decimalDelimiter);
- return displayValue;
- }
- };
-
- /**
- * Takes a user entered string and converts it to a given unit. This has some
- * extra smarts around only doing conversion if the two units are from the
- * same group. If they're not, then no conversion is done. (If you type 6cm
- * into a weight field, we just parse it down to 6.)
- * @param {string} value The value to convert.
- * @param {string} targetUnits The unit we want the value in.
- * @param {string} opt_decimalDelimiter Optional decimal delimiter to parse
- * in context of. For example, if ',' is passed, then the value 5,5 will be
- * correctly interpreted as 5.5.
- * @return {string|number} The converted value, unformatted.
- */
- conv.parseTo = function(value, targetUnits, opt_decimalDelimiter) {
- if (su.isEmpty(value)) {
- return '';
- }
- var cleanValue = value + '';
-
- // If our value is being parsed down to a number, clean up the user-entered
- // string to use periods for decimal delimiters so JS can understand it.
- if (targetUnits != 'STRING') {
- if (opt_decimalDelimiter == ',') {
- cleanValue = cleanValue.replace(/\./, '');
- cleanValue = cleanValue.replace(/\,/, '.');
- }
- if (targetUnits == 'DOLLARS' || targetUnits == 'YEN' ||
- targetUnits == 'EUROS') {
- // Strip off leading non-digit characters, to allow for entry with
- // currency amounts at the front.
- cleanValue = cleanValue.replace(/^[^\d\-]/, '');
- }
- }
- // Figure out the units involved.
- var valueUnits = conv.recognizeUnits(cleanValue);
-
- // If there were no specific unit match based on the string entered, then
- // best choice is to assume they entered the target units.
- if (valueUnits == 'STRING') {
- valueUnits = targetUnits;
- }
- var valueInBase = conv.toBase(cleanValue, valueUnits);
- var valueGroup = conv.unitsHash[valueUnits].group;
- var targetGroup = conv.unitsHash[targetUnits].group;
-
- // If these two units are in the same group, then we know how to convert.
- // If they're not, then we'll just return a clean (untemplated) value in
- // the original units.
- if (valueGroup == targetGroup) {
- return conv.fromBase(valueInBase, targetUnits);
- } else if (targetUnits == 'STRING') {
- return value;
- } else {
- return conv.toNumber(cleanValue);
- }
- };
-
- /**
- * Rounds the fractional portion of a value to the desired number of digits.
- * This method differs from toFixed in that it returns a string and explicitly
- * chops off trailing zeroes. So if you pass it "75.0", as Ruby is wont to do,
- * it will return a nice "75".
- * @param {string|number} value The value to process.
- * @param {number} digits The number of fractional digits to preserve.
- * @return {string} The newly processed value.
- */
- conv.roundDecimalPoints = function(value, digits) {
- if (su.isNumber(value)) {
- value = conv.toNumber(value);
- var returnVal = value.toFixed(digits);
- if (digits > 0) {
- // Strip off trailing zeroes and trailing decimal.
- returnVal = returnVal.replace(/0+$/, '');
- returnVal = returnVal.replace(/\.$/, '');
- }
- return returnVal;
- }
-
- if (su.isString(value)) {
- // Note that we will only ever hit this point of making a recursive call
- // if the original passed value is not a number. So by running toNumber
- // here we're guaranteeing that we won't have an infinite loop.
- return conv.roundDecimalPoints(conv.toNumber(value), digits);
- } else {
- return 0;
- }
- };
-
- /**
- * Processes anything into a number.
- * @param {string|number} value The value to process.
- * @return {number} The newly processed value.
- */
- conv.toNumber = function(value) {
- var strippedValue = value + '';
- strippedValue = strippedValue.replace(/\,/gi, '.');
- var returnValue = parseFloat(strippedValue);
- if (isNaN(returnValue)) {
- returnValue = 0;
- }
- return returnValue;
- };
-
- /**
- * Formats a currency value. NOTE that no currency conversion is performed.
- * @param {string|number} value The currency value to format.
- * @param {string} opt_prefix The currency character to start with.
- * @param {string} opt_suffix The currency character to end with.
- * @param {string} opt_dividerChar The character to split long strings with,
- * typically ','.
- * @param {string} opt_decimalDelimiter The character to use for the decimal
- * char typically '.'.
- * @return {string} The formatted value, or '' if the value is empty.
- */
- conv.formatCurrency = function(value, opt_prefix, opt_suffix, opt_dividerChar,
- opt_decimalDelimiter) {
- if (su.isEmpty(value)) {
- return '';
- }
- var prefix = su.ifEmpty(opt_prefix, '');
- var suffix = su.ifEmpty(opt_suffix, '');
- var decimal = su.ifEmpty(opt_decimalDelimiter, skp.decimalDelimiter());
-
- // Get our numeric values.
- var val = conv.toNumber(value);
-
- var whole = Math.floor(val);
- // Compute fraction, 0-padding if < 10.
- var fraction = Math.round((val - whole) * 100);
- if (fraction == 100) {
- fraction = 0;
- whole += 1;
- }
- fraction = (fraction < 10) ? '0' + fraction : fraction;
-
- if (!su.isEmpty(opt_dividerChar)) {
- whole = whole + '';
- var formattedWhole = '';
- var count = 0;
- for (var i = whole.length - 1; i >= 0; i--) {
- if (count == 3) {
- formattedWhole = opt_dividerChar + formattedWhole;
- count = 0;
- }
- count++;
- formattedWhole = whole.charAt(i) + formattedWhole;
- }
- } else {
- formattedWhole = whole;
- }
- if (whole > 999 && fraction == '00') {
- return prefix + formattedWhole + suffix;
- } else {
- return prefix + formattedWhole + decimal + fraction + suffix;
- }
- };
-
- /**
- * Does a floating-point aware assessment of whether two values are identical
- * enough to count as equal. This must be smart enough to compare a string to
- * a number and return true if the string contains a number that is close
- * enough to pass.
- * @param {Object} value1 The 1st value to compare.
- * @param {Object} value2 The 2nd value to compare.
- * @return {boolean} Whether they are equal within a reasonable tolerance.
- */
- conv.isEqual = function(value1, value2) {
- if (value1 == value2) {
- return true;
- } else if (conv.containsNumber(value1) && conv.containsNumber(value2)) {
- if (Math.abs(conv.toNumber(value1) - conv.toNumber(value2)) < .0000001) {
- return true;
- }
- }
- return false;
- };
-
- /**
- * Checks a string to see if it contains a number. For example, '1.234'
- * returns true, whereas 'Google' returns false.
- * @param {string} value The value to check.
- * @return {boolean} Whether it contains a number.
- */
- conv.containsNumber = function(value) {
- var stringValue = value + '';
- return stringValue.match(/^\d+\.*\d*$/);
- };
-
- /**
- * Takes a user-entered string and attempts to return its value
- * as a float, parsing for fractions such as 4 5/8 or 4-5/8. Note that this
- * method will raise an error if a division by zero is attempted.
- * @param {string} value The units string, such as "INCHES".
- * @return {float} The parsed value.
- */
- conv.parseFraction = function(value) {
- var wholeNumber = 0;
-
- // Look for a slash to indicate a fraction.
- if (value.match(/\//)) {
- var splitValues = value.split(/\//);
- var dividend = splitValues[0];
- var divisor = splitValues[1];
-
- // Clean leading spaces from the dividend to ease regular expression
- // matching below.
- dividend = dividend.replace(/^\s*/, '');
-
- // Check the dividend for white space. If we find these then
- // we must have a compound fraction like "5 5/16".
- if (dividend.match(/\d[\s\-]*\d/)) {
- splitValues = dividend.split(/\b[\s\-](?=\d)/);
- wholeNumber = splitValues[0];
- dividend = splitValues[1];
- }
- var returnValue;
- var wholeNumber = parseFloat(wholeNumber);
- var fraction = parseFloat(dividend) / parseFloat(divisor);
- if (wholeNumber < 0) {
- returnValue = wholeNumber - fraction;
- } else {
- returnValue = wholeNumber + fraction;
- }
- return returnValue;
- } else {
- return parseFloat(value);
- }
- };
-
- /**
- * Unit-specific formatting functions.
- */
-
- /**
- * Returns nicely formatted string for our INTEGER unit.
- * @param {number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.formatInteger = function(value) {
- if (su.isEmpty(value)) {
- return '';
- } else {
- return Math.floor(conv.toNumber(value), 0) + '';
- }
- };
-
- /**
- * Returns nicely formatted string for our EUROS unit.
- * @param {number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.formatBoolean = function(value) {
- if (conv.toNumber(value) > 0) {
- return su.translateString('TRUE');
- } else {
- return su.translateString('FALSE');
- }
- };
-
- /**
- * Returns nicely formatted string for our DOLLARS unit.
- * @param {number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.formatDollars = function(value) {
- return conv.formatCurrency(value, '$');
- };
-
- /**
- * Returns nicely formatted string for our YEN unit.
- * @param {number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.formatYen = function(value) {
- return conv.formatCurrency(value, '¥');
- };
-
- /**
- * Returns nicely formatted string for our EUROS unit.
- * @param {number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.formatEuros = function(value) {
- return conv.formatCurrency(value, '€');
- };
-
- /**
- * Unit-specific toBase functions.
- */
-
- /**
- * Converts an entered value to the appropriate base units.
- * @param {string|number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.booleanToBase = function(value) {
- var stringValue = value + '';
- if (stringValue.toLowerCase() == 'true') {
- return 1;
- } else if (stringValue.toLowerCase() == 'false') {
- return 0;
- } else {
- var numberValue = conv.toNumber(value);
- if (numberValue > 0) {
- return 1;
- } else {
- return 0;
- }
- }
- };
-
- /**
- * Converts an entered value to the appropriate base units.
- * @param {string|number} value The value to format.
- * @return {string} The newly formatted value.
- */
- conv.feetToBase = function(value) {
- var stringValue = value + '';
- var splitValues = stringValue.split(/\'/);
- var feet = su.ifEmpty(splitValues[0], '0');
- var inches = su.ifEmpty(splitValues[1], '0');
- // Parse the remaining strings for fractions.
- feet = conv.parseFraction(feet);
- inches = conv.parseFraction(inches);
- if (feet < 0) {
- return feet * 12 - inches;
- } else {
- return feet * 12 + inches;
- }
- };
-