home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Graphisme / GoogleSketchUp / GoogleSketchUpWFR.exe / GoogleSketchUp8.msi / SketchUpMeta.cab / components.js.78D17A5F_0E0A_44D2_877D_2C56D45D16B7 < prev    next >
Encoding:
Text File  |  2010-08-26  |  34.6 KB  |  1,025 lines

  1. //  Copyright: Copyright 2008 Google Inc.
  2. //  License: All Rights Reserved.
  3.  
  4. /**
  5.  * @fileoverview  DynamicComponent-specific WebDialog support functions. The
  6.  * operations provided here are shared by both author and consumer panel
  7.  * code (configurator and manager).
  8.  */
  9.  
  10. // Declare su namespace.
  11. var su = window.su;
  12.  
  13. // Declare skp namespace.
  14. var skp = window.skp;
  15.  
  16. // Declare conv namespace.
  17. var conv = window.conv;
  18.  
  19. // Declare mgr namespace.
  20. var mgr = window.mgr;
  21.  
  22. // Define a "components" object we can hang our functionality on.
  23. var comp = {};
  24.  
  25. // Common user-interface visible strings.
  26. comp.DICTIONARY = 'dynamic_attributes';
  27. comp.FORMULA_PREFIX = '=';
  28. comp.FORM_DESIGN_GROUP = 'Form Design';
  29. comp.METADATA_GROUP = 'Component Info';
  30. comp.BEHAVIORS_GROUP = 'Behaviors';
  31.  
  32. // Reserved attribute metadata.
  33. comp.RESERVED = {
  34.   name: { label: 'Name', group: comp.METADATA_GROUP, unitGroup: 'STRING',
  35.     summary: 'Friendly name of the object.'},
  36.   summary: { label: 'Summary', group: comp.METADATA_GROUP, unitGroup: 'STRING',
  37.     summary: 'Brief summary text of the component.'},
  38.   description: { label: 'Description', group: comp.METADATA_GROUP,
  39.     unitGroup: 'STRING', summary: 'Description of the component.'},
  40.   itemcode: { label: 'ItemCode', group: comp.METADATA_GROUP,
  41.     unitGroup: 'STRING', summary: 'Unique numeric identifier.'},
  42.  
  43.   x: { label: 'X', group: 'Position', unitGroup: 'LENGTH',
  44.     summary: 'Position along the red axis, in inches.',
  45.     hasLiveValue: true, color: '#B20000'},
  46.   y: { label: 'Y', group: 'Position', unitGroup: 'LENGTH',
  47.     summary: 'Position along the green axis, in inches.',
  48.     hasLiveValue: true, color: '#00B200'},
  49.   z: { label: 'Z', group: 'Position', unitGroup: 'LENGTH',
  50.     summary: 'Position along the blue axis, in inches.',
  51.     hasLiveValue: true, color: '#0000B2'},
  52.  
  53.   lenx: { label: 'LenX', group: 'Size', unitGroup: 'LENGTH',
  54.     summary: 'Size along the red axis, in inches.',
  55.     hasLiveValue: true, color: '#B20000'},
  56.   leny: { label: 'LenY', group: 'Size', unitGroup: 'LENGTH',
  57.     summary: 'Size along the green axis, in inches.',
  58.     hasLiveValue: true, color: '#00B200'},
  59.   lenz: { label: 'LenZ', group: 'Size', unitGroup: 'LENGTH',
  60.     summary: 'Size along the blue axis, in inches.',
  61.     hasLiveValue: true, color: '#0000B2'},
  62.  
  63.   rotx: { label: 'RotX', group: 'Rotation', unitGroup: 'ANGLE',
  64.     summary: 'Rotation about the red axis, in degrees.',
  65.     hasLiveValue: true, color: '#B20000'},
  66.   roty: { label: 'RotY', group: 'Rotation', unitGroup: 'ANGLE',
  67.     summary: 'Rotation about the green axis, in degrees.',
  68.     hasLiveValue: true, color: '#00B200'},
  69.   rotz: { label: 'RotZ', group: 'Rotation', unitGroup: 'ANGLE',
  70.     summary: 'Rotation about the blue axis, in degrees.',
  71.     hasLiveValue: true, color: '#0000B2'},
  72.  
  73.   material: { label: 'Material', group: comp.BEHAVIORS_GROUP,
  74.     hasLiveValue: true, summary: 'Such as "Green" or ' +
  75.       '"#FF0000".', unitGroup: 'STRING', units: 'STRING' },
  76.   scaletool: { label: 'ScaleTool', group: comp.BEHAVIORS_GROUP,
  77.     summary: 'Controls which scale tool handles appear.' },
  78.   hidden: { label: 'Hidden', group: comp.BEHAVIORS_GROUP,
  79.     unitGroup: 'BOOLEAN', units: 'BOOLEAN',
  80.     summary: 'If greater than 0, component will be hidden.' },
  81.   onclick: { label: 'onClick', group: comp.BEHAVIORS_GROUP,
  82.     unitGroup: 'STRING',
  83.     summary: 'Contains scripting commands that run when clicked.'},
  84.   copies: { label: 'Copies', group: comp.BEHAVIORS_GROUP,
  85.     unitGroup: 'INTEGER', units: 'INTEGER',
  86.     summary: 'How many duplicates of this part to create.'},
  87.  
  88.   copy: { label: 'COPY', group: 'Read Only',
  89.     summary: 'COPY is a readonly attribute that contains the part's ' +
  90.       'copy number.', isHidden: true},
  91.  
  92.   imageurl: { label: 'ImageURL', group: comp.FORM_DESIGN_GROUP,
  93.     unitGroup: 'STRING', units: 'STRING',
  94.     summary: 'Contains a URL to a thumbnail image.'},
  95.   dialogwidth: { label: 'DialogWidth', group: comp.FORM_DESIGN_GROUP,
  96.     unitGroup: 'INTEGER', defaultValue: '345', units: 'INTEGER',
  97.     summary: 'Width of the Component Options dialog in pixels.'},
  98.   dialogheight: { label: 'DialogHeight', group: comp.FORM_DESIGN_GROUP,
  99.     unitGroup: 'INTEGER', defaultValue: '560', units: 'INTEGER',
  100.     summary: 'Height of the Component Options dialog in pixels.'}
  101. };
  102.  
  103.  
  104. /**
  105.  * Installs a handler for keydown in a cross-browser (IE and WebKit) fashion.
  106.  * @param {string} eventName The event name in one of three formats: short,
  107.  *     standard, and long. For example, the keydown event can be expressed as
  108.  *     'down', 'keydown', or 'onkeydown'.
  109.  * @param {Function} handler The function used to respond to keydown events.
  110.  */
  111. comp.installKeyHandler = function(eventName, handler) {
  112.   var name = eventName.toLowerCase();
  113.  
  114.   switch (name)
  115.   {
  116.     case 'down':
  117.     case 'keydown':
  118.     case 'onkeydown':
  119.       name = 'keydown';
  120.       break;
  121.     case 'press':
  122.     case 'keypress':
  123.     case 'onkeypress':
  124.       name = 'keypress';
  125.       break;
  126.     case 'up':
  127.     case 'keyup':
  128.     case 'onkeyup':
  129.       name = 'keyup';
  130.       break;
  131.     default:
  132.       // Not in the valid list, that's a problem.
  133.       su.raise(su.translateString('Invalid value for event name: ') +
  134.           eventName);
  135.       return;
  136.   }
  137.  
  138.   // Now we do the actual handler installation in a platform-specific way.
  139.   if (su.IS_MAC) {
  140.     document.documentElement.addEventListener(name, handler, true);
  141.   } else {
  142.     document.documentElement.attachEvent('on' + name, handler);
  143.   }
  144. };
  145.  
  146. /**
  147.  * Converts a value to the default magnitude for the units provided. Here are 
  148.  * some example string patterns that can be parsed:
  149.  * - Feet inches, such as 6' 5 1/2", or 6' 5", or 1'4-1/2"
  150.  * - Fractional inches, such as 3/4", or 1-5/8", or 12 1/2"
  151.  * - Decimal millimeters and meters, such as 45mm, or 6.4 m, or 12.1 mm
  152.  * We returns zero if the value can't be parsed into anything useful, since
  153.  * the user could enter anything, such as "bob", and the best recourse we have
  154.  * is to return 0. The one exception to this is if the user is editing a field
  155.  * that is inherently a string, in which case we simply return the string.
  156.  * @param {string|number} value The value to convert.
  157.  * @param {string} units The units to convert to.
  158.  * @return {string} The formatted value.
  159.  */
  160. comp.convertToDefault = function(value, units) {
  161.   var val;
  162.  
  163.   // Necessary to handle unicode characters entered directly into the text
  164.   // field. (Except for a few characters which we allow).
  165.   val = encodeURIComponent(value);
  166.   val = val.replace(/\%3D/gi, '=');
  167.   val = val.replace(/\%2C/gi, ',');
  168.   val = val.replace(/\%22/gi, '"');
  169.   val = val.replace(/\%20/gi, ' ');
  170.   val = val.replace(/\%2F/gi, '/');
  171.   val = val.replace(/\%../gi, '');
  172.  
  173.   if (units == 'STRING') {
  174.     return val;
  175.   }
  176.  
  177.   var returnVal = comp.parseLengthIntoInches(val, units);
  178.   if (su.isNumber(returnVal)) {
  179.     return returnVal;
  180.   } else {
  181.     return 0;
  182.   }
  183.  
  184. };
  185.  
  186. /**
  187.  * Takes a user-entered length string and attempts to return its value in 
  188.  * inches, as a float.
  189.  * @param {string} value The units string, such as "INCHES". (The full list
  190.  *     of valid unit types can be verified in manager.js).
  191.  * @param {string} defaultUnits The units to default to if none are 
  192.  *     found inside the string.
  193.  * @return {float} The length in inches.
  194.  */
  195. comp.parseLengthIntoInches = function(value, defaultUnits) {
  196.   var units;
  197.   var returnValue = parseFloat(value);
  198.  
  199.   if (value.indexOf("'") > -1) {
  200.     units = 'FEET'
  201.   } else if (value.match(/\d\s*cm/i)) {
  202.     units = 'CENTIMETERS'
  203.   } else if (value.match(/\d\s*mm/i)) {
  204.     units = 'MILLIMETERS'
  205.   } else if (value.match(/\d\s*m/i)) {
  206.     units = 'METERS'
  207.   } else if (value.match(/\d\s*\"/i)) {
  208.     units = 'INCHES'
  209.   } else {
  210.     units = defaultUnits;
  211.   }
  212.  
  213.   switch (units) {
  214.     case 'FEET':
  215.       var splitValues = value.split(/\'/)
  216.       var feet = su.ifEmpty(splitValues[0], '0');
  217.       var inches = su.ifEmpty(splitValues[1], '0');
  218.       // Parse the remaining strings for fractions.
  219.       feet = comp.parseFraction(feet);
  220.       inches = comp.parseFraction(inches);
  221.       return feet * 12 + inches;
  222.  
  223.     case 'METERS':
  224.       return parseFloat(value) * 39.3700787;
  225.     case 'MILLIMETERS':
  226.       return parseFloat(value) / 25.4;
  227.     case 'CENTIMETERS':
  228.       return parseFloat(value) / 2.54;
  229.     case 'INCHES':
  230.       return comp.parseFraction(value);
  231.     default:
  232.       return parseFloat(value);
  233.   }
  234.  
  235. };
  236.  
  237. /**
  238.  * Takes a user-entered string and attempts to return its value  
  239.  * as a float, parsing for fractions such as 4 5/8 or 4-5/8. Note that this
  240.  * method will raise an error if a division by zero is attempted.
  241.  * @param {string} value The units string, such as "INCHES".
  242.  * @return {float} The parsed value.
  243.  */
  244. comp.parseFraction = function(value) {
  245.   var wholeNumber = 0;
  246.  
  247.   // Look for a slash to indicate a fraction.
  248.   if (value.match(/\//)) {
  249.     var splitValues = value.split(/\//);
  250.     var dividend = splitValues[0];
  251.     var divisor = splitValues[1];
  252.  
  253.     // Clean leading spaces from the dividend to ease regular expression
  254.     // matching below.
  255.     dividend = dividend.replace(/^\s*/, '');
  256.  
  257.     // Check the dividend for white space. If we find these then
  258.     // we must have a compound fraction like "5 5/16".
  259.     if (dividend.match(/\d[\s\-]*\d/)) {
  260.       splitValues = dividend.split(/\b[\s\-](?=\d)/);
  261.       wholeNumber = splitValues[0];
  262.       dividend = splitValues[1];
  263.     }
  264.     return parseFloat(wholeNumber) + parseFloat(dividend) / parseFloat(divisor);
  265.   } else {
  266.     return parseFloat(value);
  267.   }
  268. };
  269.  
  270. /**
  271.  * Escapes text to avoid problems with embedded quotes.
  272.  * @param {string} text The string to escape.
  273.  * @return {string} The escaped string.
  274.  */
  275. comp.escapeQuotes = function(text) {
  276.   return text.replace(/"/g, '"'
  277.     ).replace(/'/g, ''');
  278. };
  279.  
  280. /**
  281.  * Takes a user entered string, and calculates an appropriate "base" value
  282.  * that matches an attribute. For example, if the attribute in question is "X",
  283.  * and that attribute has its formulaunits set to "INCHES", then text entry
  284.  * of "25.4mm" would return 1.0.
  285.  * @param {string|number} value The value to convert.
  286.  * @param {Object} entity The entity to look in.
  287.  * @param {string} attribute Name of the attribute.
  288.  * @return {string|number} The base value.
  289.  */
  290. comp.parseToBase = function(value, entity, attribute) {
  291.   var units = comp.getAttributeFormulaUnits(entity, attribute, true);
  292.   var enteredValue = conv.parseTo(value, units);
  293.   var baseValue = conv.toBase(enteredValue, units);
  294.   return baseValue;
  295. };
  296.  
  297. /**
  298.  * Takes an attribute and displays it in its correctly formatted, unitized
  299.  * value.
  300.  * @param {Object} entity The entity to look in.
  301.  * @param {string} attribute Name of the attribute.
  302.  * @param {number} opt_decimalPlaces Number of decimal places to round to.
  303.  * @return {string|number} The converted value.
  304.  */
  305. comp.getAttributeFormattedValue = function(entity, attribute,
  306.     opt_decimalPlaces) {
  307.   var attr = comp.getAttribute(entity, attribute);
  308.   var units = comp.getAttributeFormulaUnits(entity, attribute, true);
  309.   var convertedValue = conv.fromBase(attr.value, units);
  310.   var value = conv.format(convertedValue, units, opt_decimalPlaces);
  311.   return value;
  312. };
  313.  
  314. /**
  315.  * Processes a string of text which will ultimately become part of an
  316.  * element's content. This means the string undergoes certain filtering for
  317.  * potentially insecure content as well as entity processing as needed.
  318.  * @param {string} text The text to format.
  319.  * @return {string} The reformatted text.
  320.  */
  321. comp.formatContent = function(text) {
  322.  
  323.   // Force convert to string in case undefined is passed.
  324.   text = su.ifEmpty(text, '');
  325.  
  326.   text = su.sanitizeHTML(text);
  327.  
  328.   // Alter the string that is to displayed as HTML so that any links open in a
  329.   // new window via Ruby. Otherwise links would open inside the config window.
  330.   text = text.replace(/\"/gi, '"');
  331.   text = text.replace(/href=(?=[^\"])/gi, 'href=skp:do_open_url@url=');
  332.   text = text.replace(/href=\"/gi, 'href="skp:do_open_url@url=');
  333.   return text;
  334. };
  335.  
  336. /**
  337.  * Rounds the fractional portion of a value to the desired number of digits.
  338.  * @param {string|number} value The value to process.
  339.  * @param {number} digits The number of fractional digits to preserve.
  340.  * @return {number} The newly processed value.
  341.  */
  342. comp.roundDecimalPoints = function(value, digits) {
  343.   if (su.isNumber(value)) {
  344.     var returnVal = value.toFixed(digits) + '';
  345.     if (digits > 0) {
  346.       // Strip off trailing zeroes and trailing decimal.
  347.       returnVal = returnVal.replace(/0+$/, '');
  348.       returnVal = returnVal.replace(/\.$/, '');
  349.     }
  350.     return parseFloat(returnVal);
  351.   }
  352.  
  353.   if (su.isString(value)) {
  354.     return comp.roundDecimalPoints(parseFloat(value), digits);
  355.   } else {
  356.     return 0;
  357.   }
  358. };
  359.  
  360. //  --------------------------------------------------------------------------
  361. //  Attribute Management
  362. //  --------------------------------------------------------------------------
  363.  
  364. /**
  365.  * Returns the named attribute object from an object's dynamic component
  366.  * attribute list.
  367.  * @param {Object} entity The object to search.
  368.  * @param {string} attribute The specific attribute name to locate.
  369.  * @return {attribute} A SketchUp Attribute object.
  370.  */
  371. comp.getAttribute = function(entity, attribute) {
  372.   return su.getAttribute(entity, comp.DICTIONARY, attribute);
  373. };
  374.  
  375. /**
  376.  * Returns the dictionary of component-specific attributes for an entity.
  377.  * @param {Object} entity The object to search.
  378.  * @return {Object} The entity's dynamic component attribute dictionary.
  379.  */
  380. comp.getAttributes = function(entity) {
  381.   return su.getDictionary(entity, comp.DICTIONARY);
  382. };
  383.  
  384. /**
  385.  * Returns the named attribute's access setting from the entity's
  386.  * attribute dictionaries.
  387.  * @param {Object} entity The object to search for dictionary data.
  388.  * @param {string} attribute The name of the specific attribute to locate.
  389.  * @return {Object} The units of the named attribute.
  390.  */
  391. comp.getAttributeAccess = function(entity, attribute) {
  392.   var attr;
  393.  
  394.   attr = comp.getAttribute(entity, attribute);
  395.   if (su.isValid(attr)) {
  396.     return attr.access;
  397.   }
  398. };
  399.  
  400. /**
  401.  * Returns the named attribute's error setting from the entity's
  402.  * attribute dictionaries.
  403.  * @param {Object} entity The object to search for dictionary data.
  404.  * @param {string} attribute The name of the specific attribute to locate.
  405.  * @return {Object} The units of the named attribute.
  406.  */
  407. comp.getAttributeError = function(entity, attribute) {
  408.   var attr;
  409.  
  410.   attr = comp.getAttribute(entity, attribute);
  411.   if (su.isValid(attr)) {
  412.     return attr.error;
  413.   }
  414. };
  415.  
  416. /**
  417.  * Returns the named attribute's form label from the entity's
  418.  * attribute dictionaries.
  419.  * @param {Object} entity The object to search for dictionary data.
  420.  * @param {string} attribute The name of the specific attribute to locate.
  421.  * @return {Object} The units of the named attribute.
  422.  */
  423. comp.getAttributeFormLabel = function(entity, attribute) {
  424.   var attr;
  425.  
  426.   attr = comp.getAttribute(entity, attribute);
  427.   if (su.isValid(attr)) {
  428.     return attr.formlabel;
  429.   }
  430. };
  431.  
  432. /**
  433.  * Returns the named attribute's error value from the entity's attribute
  434.  * dictionaries.
  435.  * @param {Object} entity The object to search for dictionary data.
  436.  * @param {string} attribute The name of the specific attribute to locate.
  437.  * @return {Object} The error property of the named attribute.
  438.  */
  439. comp.getAttributeError = function(entity, attribute) {
  440.   var attr;
  441.  
  442.   attr = comp.getAttribute(entity, attribute);
  443.   if (su.isValid(attr)) {
  444.     return '' + attr.error;
  445.   } else {
  446.     return '';
  447.   }
  448. };
  449.  
  450. /**
  451.  * Returns the named attribute's formula from the entity's attribute
  452.  * dictionaries.
  453.  * @param {Object} entity The object to search for dictionary data.
  454.  * @param {string} attribute The name of the specific attribute to locate.
  455.  * @return {Object} The units of the named attribute.
  456.  */
  457. comp.getAttributeFormula = function(entity, attribute) {
  458.   var attr;
  459.  
  460.   attr = comp.getAttribute(entity, attribute);
  461.   if (su.isValid(attr)) {
  462.     return attr.formula;
  463.   }
  464. };
  465.  
  466. /**
  467.  * Returns the named attribute's label from the entity's attribute
  468.  * dictionaries.
  469.  * @param {Object} entity The object to search for dictionary data.
  470.  * @param {string} attribute The name of the specific attribute to locate.
  471.  * @return {Object} The units of the named attribute.
  472.  */
  473. comp.getAttributeLabel = function(entity, attribute) {
  474.   var attr;
  475.  
  476.   attr = comp.getAttribute(entity, attribute);
  477.   if (su.isValid(attr)) {
  478.     return attr.label;
  479.   }
  480. };
  481.  
  482. /**
  483.  * Returns the named attribute's options setting from the entity's
  484.  * attribute dictionaries.
  485.  * @param {Object} entity The object to search for dictionary data.
  486.  * @param {string} attribute The name of the specific attribute to locate.
  487.  * @return {Object} The options value of the named attribute.
  488.  */
  489. comp.getAttributeOptions = function(entity, attribute) {
  490.   var attr;
  491.  
  492.   attr = comp.getAttribute(entity, attribute);
  493.   if (su.isValid(attr)) {
  494.     return attr.options;
  495.   }
  496. };
  497.  
  498. /**
  499.  * Returns the named attribute's unit setting from the entity's
  500.  * attribute dictionaries.
  501.  * @param {Object} entity The object to search for dictionary data.
  502.  * @param {string} attribute The name of the specific attribute to locate.
  503.  * @return {Object} The units value of the named attribute.
  504.  */
  505. comp.getAttributeUnits = function(entity, attribute) {
  506.   var attr;
  507.  
  508.   attr = comp.getAttribute(entity, attribute);
  509.   if (su.isValid(attr)) {
  510.     return attr.units;
  511.   }
  512. };
  513.  
  514. /**
  515.  * Returns the named attribute's value from the entity's attribute
  516.  * dictionaries.
  517.  * @param {Object} entity The object to search for dictionary data.
  518.  * @param {string} attribute The name of the specific attribute to locate.
  519.  * @return {Object} The value of the named attribute.
  520.  */
  521. comp.getAttributeValue = function(entity, attribute) {
  522.   var attr;
  523.  
  524.   attr = comp.getAttribute(entity, attribute);
  525.   if (su.isValid(attr)) {
  526.     return attr.value;
  527.   }
  528. };
  529.  
  530. /**
  531.  * Returns the named attribute's formulaunits from the entity's attribute
  532.  * dictionaries.
  533.  * @param {Object} entity The object to search for dictionary data.
  534.  * @param {string} attribute The name of the specific attribute to locate.
  535.  * @param {boolean} useDefaults Defines whether length attributes should 
  536.  *     return the default _lengthunit or "STRING" if no formulaunit is set.
  537.  * @return {Object} The value of the named attribute.
  538.  */
  539. comp.getAttributeFormulaUnits = function(entity, attribute, useDefaults) {
  540.   var attr = comp.getAttribute(entity, attribute);
  541.   if (su.isValid(attr)) {
  542.     var units = attr.formulaunits;
  543.     if (useDefaults == true && su.isValid(comp.RESERVED[attribute])) {
  544.       units = su.ifEmpty(units, comp.RESERVED[attribute].units);
  545.       if (comp.RESERVED[attribute].unitGroup == 'LENGTH') {
  546.         units = su.ifEmpty(units, comp.lengthUnits(entity));
  547.       }
  548.     }
  549.     // If no unit is set, default to STRING as the catch all.
  550.     if (useDefaults == true) {
  551.       units = su.ifEmpty(units, 'STRING');
  552.     }
  553.     return units;
  554.   }
  555. };
  556.  
  557. /**
  558.  * Returns true if the named attribute exists in an object's dynamic
  559.  * component attribute list.
  560.  * @param {Object} entity The object to search.
  561.  * @param {string} attribute The specific attribute name to locate.
  562.  * @return {boolean} True if the named attribute exists.
  563.  */
  564. comp.hasAttribute = function(entity, attribute) {
  565.   return su.hasAttribute(entity, comp.DICTIONARY, attribute);
  566. };
  567.  
  568. /**
  569.  * Returns the lengthUnits attached to a given entity, defaulting to INCHES.
  570.  * @param {Object} entity The object to search.
  571.  * @return {string} The units string, such as INCHES or CENTIMETERS.
  572.  */
  573. comp.lengthUnits = function(entity) {
  574.   var lengthUnits = comp.getAttributeValue(entity, '_lengthunits');
  575.   lengthUnits = su.ifEmpty(lengthUnits, 'INCHES');
  576.   return lengthUnits;
  577. };
  578.  
  579. /**
  580.  * Strips any units indicators from a string.
  581.  * @param {string|number} measurement The value to strip.
  582.  * @return {string} The value stripped of everything but numbers, decimals,
  583.  *     or the negative (-) sign.
  584.  */
  585. comp.stripUnits = function(measurement) {
  586.   // Strip off escaped characters and all non-digit or non-decimal characters.
  587.   var returnValue = measurement + '';
  588.   returnValue = returnValue.replace(/\&.+\;/gi, '');
  589.   returnValue = returnValue.replace(/[^0-9,\.]/gi, '');
  590.   return returnValue;
  591. };
  592.  
  593. /**
  594.  * Retrieves the live value for an attribute from SketchUp.
  595.  * @param {Object} entity The object to pull attribute data for.
  596.  * @param {string} attribute The name of the attribute to fetch data for.
  597.  * @param {string} success The name of any onsuccess callback.
  598.  * @param {string} failure The name of any onfailure callback.
  599.  * @param {string} complete The name of any oncomplete callback.
  600.  */
  601. comp.pullLiveValue = function(entity, attribute, success, failure, complete) {
  602.   su.callRuby('pull_live_value',
  603.     {'id': entity.id,
  604.     'name': attribute,
  605.     'onfailure': failure,
  606.     'onsuccess': success,
  607.     'oncomplete': complete});
  608. };
  609.  
  610. /**
  611.  * Pushes all aspects of an attribute across the Ruby/JavaScript bridge.
  612.  * @param {Object} entity The object whose attribute should be pushed.
  613.  * @param {string} attribute The name of the attribute to push.
  614.  * @param {Function} opt_onComplete An optional function to call on
  615.  *     completion.
  616.  */
  617. comp.pushAttribute = function(entity, attribute, opt_onComplete) {
  618.   var attr;
  619.   var params;
  620.  
  621.   if (su.notValid(entity)) {
  622.     su.raise(su.translateString('Invalid entity. Attribute push cancelled.'));
  623.     return;
  624.   }
  625.  
  626.   attr = comp.getAttribute(entity, attribute);
  627.   if (su.notValid(attr)) {
  628.     su.raise(su.translateString('Attribute not found: ') + attribute);
  629.     return;
  630.   }
  631.  
  632.   params = {'id': entity.id,
  633.     'dictionary': 'dynamic_attributes',
  634.     'name': attribute,
  635.     'value': comp.getAttributeValue(entity, attribute),
  636.     'formula': comp.getAttributeFormula(entity, attribute),
  637.     'label': comp.getAttributeLabel(entity, attribute),
  638.     'access': comp.getAttributeAccess(entity, attribute),
  639.     'options': comp.getAttributeOptions(entity, attribute),
  640.     'formlabel': comp.getAttributeFormLabel(entity, attribute),
  641.     'units': comp.getAttributeUnits(entity, attribute),
  642.     'formulaunits': comp.getAttributeFormulaUnits(entity, attribute, false)
  643.   };
  644.  
  645.   // If this is being called inside the manager, then send down the root
  646.   // entityID so it is always redrawn.
  647.   if (mgr != undefined) {
  648.     params['redraw_id'] = mgr.rootEntity.id;
  649.   }
  650.  
  651.   if (su.isDefined(opt_onComplete)) {
  652.     params['oncomplete'] = opt_onComplete;
  653.   }
  654.   su.callRuby('push_attribute', params);
  655. };
  656.  
  657. /**
  658.  * Pushes a new name for an attribute across the ruby bridge
  659.  * @param {Object} entity The object whose attribute should be pushed.
  660.  * @param {string} attribute The name of the attribute to push.
  661.  * @param {string} newName The attribute's new name.
  662.  * @param {string} newLabel The label value to use for the attribute.
  663.  * @param {Function} opt_onComplete An optional function to call on
  664.  *     completion.
  665.  */
  666. comp.pushRename = function(entity, attribute, newName, newLabel, 
  667.     opt_onComplete) {
  668.   var attr;
  669.   var params;
  670.  
  671.   if (su.notValid(entity)) {
  672.     su.raise(su.translateString('Invalid entity. Attribute push cancelled.'));
  673.     return;
  674.   }
  675.  
  676.   attr = comp.getAttribute(entity, attribute);
  677.   if (su.notValid(attr)) {
  678.     su.raise(su.translateString('Attribute not found: ') + attribute);
  679.     return;
  680.   }
  681.  
  682.   params = {'id': entity.id,
  683.     'dictionary': 'dynamic_attributes',
  684.     'name': attribute,
  685.     'new_name': newName,
  686.     'new_label': newLabel};
  687.  
  688.   if (su.isDefined(opt_onComplete)) {
  689.     params['oncomplete'] = opt_onComplete;
  690.   }
  691.   su.callRuby('push_rename', params);
  692. };
  693.  
  694. /**
  695.  * Commits a set of attribute values to one or more entities by pushing their
  696.  * IDs and values across the Ruby/JavaScript bridge.
  697.  * @param {string|Array} entities The SketchUp entity IDs being updated.
  698.  * @param {Object} attributes A set of key/value pairs (in object form)
  699.  *     whose values should be pushed.
  700.  * @param {Function} opt_onComplete An optional function to call on
  701.  *     completion.
  702.  * @param {boolean} opt_noRedraw True to force no redrawing on completion.
  703.  */
  704. comp.pushAttributeSet = function(entities, attributes, opt_onComplete,
  705.     opt_noRedraw) {
  706.   var params;
  707.   var items;
  708.   var len;
  709.   var i;
  710.   var j;
  711.   var name;
  712.   var parts;
  713.   var value;
  714.   var formula;
  715.   var subItems;
  716.  
  717.   params = {};
  718.  
  719.   items = su.getItems(attributes);
  720.  
  721.   len = items.length;
  722.   for (i = 0; i < len; i++) {
  723.     // We use escaped key/value content as pairs, which are iterated over on
  724.     // the other side of the bridge.
  725.     name = items[i][0];
  726.     if ((/__/).test(name)) {
  727.       parts = name.split('__');
  728.       name = parts[parts.length - 1];
  729.     }
  730.     value = items[i][1];
  731.  
  732.     // If just a string is passed, then assume it contains the value, or a
  733.     // formula if it starts with an '='. If it's not a string, then assume it
  734.     // is an associative array broken our into our "meta" attributes.
  735.     if (su.isString(value)) {
  736.       params[name + '__value'] = value;
  737.       // Formulas start with a specific prefix (typically '=').
  738.       if (value.indexOf(comp.FORMULA_PREFIX) == 0) {
  739.         formula = value.slice(comp.FORMULA_PREFIX.length);
  740.         params[name + '__formula'] = formula;
  741.       }
  742.     } else {
  743.       subItems = su.getItems(value);
  744.       for (j = 0; j < subItems.length; j++) {
  745.         params[name + '__' + subItems[j][0]] = subItems[j][1];
  746.       }
  747.     }
  748.   }
  749.  
  750.   // Now we have a few "administrative" attributes to put in the query.
  751.   params['$ids'] = entities;
  752.  
  753.   if (opt_noRedraw == true) {
  754.     params['$no_redraw'] = 1;
  755.    }
  756.  
  757.   if (su.isDefined(opt_onComplete)) {
  758.     params['oncomplete'] = opt_onComplete;
  759.   }
  760.  
  761.   su.callRuby('push_attribute_set', params);
  762.  
  763. };
  764.  
  765. /**
  766.  * Commits an attribute to the SketchUp model by pushing the attribute value
  767.  * across the Ruby/JavaScript bridge.
  768.  * @param {string} entityID The SketchUp entity ID being updated.
  769.  * @param {string} attribute The name of the attribute to update.
  770.  * @param {Object} value The value to set for the attribute.
  771.  * @param {Function} opt_onComplete An optional function to call on
  772.  *     completion.
  773.  * @param {boolean} opt_noRedraw True to force no redrawing on completion.
  774.  */
  775. comp.pushAttributeValue = function(entityID, attribute, value, opt_onComplete,
  776.     opt_noRedraw) {
  777.  
  778.   var params;
  779.   var formula;
  780.  
  781.   params = {};
  782.  
  783.   params['dictionary'] = comp.DICTIONARY;
  784.  
  785.   if (opt_noRedraw == true) {
  786.     params['no_redraw'] = 1;
  787.   }
  788.  
  789.   params['id'] = entityID;
  790.   params['name'] = attribute;
  791.   params['value'] = value;
  792.  
  793.   // Formulas start with a specific prefix (typically '=').
  794.   if (value.indexOf(comp.FORMULA_PREFIX) == 0) {
  795.     formula = value.slice(comp.FORMULA_PREFIX.length);
  796.     params['formula'] = formula;
  797.   }
  798.  
  799.   if (su.isDefined(opt_onComplete)) {
  800.     params['oncomplete'] = opt_onComplete;
  801.   };
  802.  
  803.   su.callRuby('push_attribute', params);
  804. };
  805.  
  806. /**
  807.  * Pushes an entity across the Ruby/JavaScript bridge.
  808.  * @param {string} id The ID of the entity to push. 
  809.  * @param {Event} evt The native event, if available.
  810.  * @return {boolean} False, to preserve UI which invokes this function.
  811.  */
  812. comp.pushSelection = function(id, evt) {
  813.   if (su.canCall(evt, 'stopPropagation')) {
  814.     evt.stopPropagation();
  815.   } else if (su.isValid(window.event)) {
  816.     window.event.cancelBubble = true;
  817.   }
  818.  
  819.   // If no id was passed, then default to an empty string. This tells SketchUp
  820.   // to clear the selection when it receives this callback.
  821.   id = su.ifEmpty(id, '');
  822.   su.callRuby('push_selection', {'id': id});
  823.  
  824.   // Return false to avoid having UI-initiated calls clear the panel.
  825.   return false;
  826. };
  827.  
  828. /**
  829.  * Sets a particular aspect (the key) of an attribute. The target attribute
  830.  * dictionary is the components dictionary. NOTE that the dictionary and
  831.  * attribute are created if necessary.
  832.  * @param {Object} entity The object to search for the attribute
  833.  *     dictionary.
  834.  * @param {string} attribute The name of the attribute to update.
  835.  * @param {string} key The name of the aspect to update.
  836.  * @param {Object} value The value to set for the key.
  837.  * @return {Object} The value after the set has been processed.
  838.  */
  839. comp.setAttribute = function(entity, attribute, key, value) {
  840.   return su.setAttribute(entity, comp.DICTIONARY, attribute, key, value);
  841. };
  842.  
  843. /**
  844.  * Sets the access value for the entity attribute provided.
  845.  * @param {Object} entity The object to search for dictionary data.
  846.  * @param {string} attribute The name of the specific attribute to locate.
  847.  * @param {string} value The new value to set for the property.
  848.  * @return {Object} The value after the set has been processed.
  849.  */
  850. comp.setAttributeAccess = function(entity, attribute, value) {
  851.   return comp.setAttribute(entity, attribute, 'access', value);
  852. };
  853.  
  854. /**
  855.  * Sets the error value for the entity attribute provided.
  856.  * @param {Object} entity The object to search for dictionary data.
  857.  * @param {string} attribute The name of the specific attribute to locate.
  858.  * @param {string} value The new value to set for the property.
  859.  * @return {Object} The value after the set has been processed.
  860.  */
  861. comp.setAttributeError = function(entity, attribute, value) {
  862.   return comp.setAttribute(entity, attribute, 'error', value);
  863. };
  864.  
  865. /**
  866.  * Sets the form label for the entity attribute provided.
  867.  * @param {Object} entity The object to search for dictionary data.
  868.  * @param {string} attribute The name of the specific attribute to locate.
  869.  * @param {string} value The new value to set for the property.
  870.  * @return {Object} The value after the set has been processed.
  871.  */
  872. comp.setAttributeFormLabel = function(entity, attribute, value) {
  873.   return comp.setAttribute(entity, attribute, 'formlabel', value);
  874. };
  875.  
  876. /**
  877.  * Sets the formula value for the entity attribute provided.
  878.  * @param {Object} entity The object to search for dictionary data.
  879.  * @param {string} attribute The name of the specific attribute to locate.
  880.  * @param {string} value The new value to set for the property.
  881.  * @return {Object} The value after the set has been processed.
  882.  */
  883. comp.setAttributeFormula = function(entity, attribute, value) {
  884.   return comp.setAttribute(entity, attribute, 'formula', value);
  885. };
  886.  
  887. /**
  888.  * Sets the label for the entity attribute provided.
  889.  * @param {Object} entity The object to search for dictionary data.
  890.  * @param {string} attribute The name of the specific attribute to locate.
  891.  * @param {string} value The new value to set for the property.
  892.  * @return {Object} The value after the set has been processed.
  893.  */
  894. comp.setAttributeLabel = function(entity, attribute, value) {
  895.   return comp.setAttribute(entity, attribute, 'label', value);
  896. };
  897.  
  898. /**
  899.  * Sets the options value for the entity attribute provided.
  900.  * @param {Object} entity The object to search for dictionary data.
  901.  * @param {string} attribute The name of the specific attribute to locate.
  902.  * @param {string} value  The new value to set for the property.
  903.  * @return {Object} The value after the set has been processed.
  904.  */
  905. comp.setAttributeOptions = function(entity, attribute, value) {
  906.   return comp.setAttribute(entity, attribute, 'options', value);
  907. };
  908.  
  909. /**
  910.  * Sets the units value for the entity attribute provided.
  911.  * @param {Object} entity The object to search for dictionary data.
  912.  * @param {string} attribute The name of the specific attribute to locate.
  913.  * @param {string} value The new value to set for the property.
  914.  * @return {Object} The value after the set has been processed.
  915.  */
  916. comp.setAttributeUnits = function(entity, attribute, value) {
  917.   return comp.setAttribute(entity, attribute, 'units', value);
  918. };
  919.  
  920. /**
  921.  * Sets the value property for the entity attribute provided.
  922.  * @param {Object} entity The object to search for dictionary data.
  923.  * @param {string} attribute The name of the specific attribute to locate.
  924.  * @param {string} value The new value to set for the property.
  925.  * @return {Object} The value after the set has been processed.
  926.  */
  927. comp.setAttributeValue = function(entity, attribute, value) {
  928.   return comp.setAttribute(entity, attribute, 'value', value);
  929. };
  930.  
  931. /**
  932.  * Sets the formulaUnits value for the entity attribute provided.
  933.  * @param {Object} entity The object to search for dictionary data.
  934.  * @param {string} attribute The name of the specific attribute to locate.
  935.  * @param {string} value The new formulaUnits to set for the property.
  936.  * @return {Object} The value after the set has been processed.
  937.  */
  938. comp.setAttributeFormulaUnits = function(entity, attribute, value) {
  939.   return comp.setAttribute(entity, attribute, 'formulaunits', value);
  940. };
  941.  
  942. /**
  943.  * Pulls attribute data from the current SketchUp selection object,
  944.  * populating the root entity used by other component panel routines.
  945.  * You can provide an oncomplete handler to process the results, which are
  946.  * placed in the comp.rootEntity property for consumption.
  947.  * @param {Object} request An object whose key/value pairs provide
  948.  *   parameters to the call.
  949.  */
  950. comp.pullAttributes = function(request) {
  951.   //  default the success and failure handlers to update rootEntity data
  952.   su.addIfAbsent(request, 'onsuccess', 'comp.handlePullAttributesSuccess');
  953.   su.addIfAbsent(request, 'onfailure', 'comp.handlePullAttributesFailure');
  954.  
  955.   su.callRuby('pull_attribute_tree', request);
  956. };
  957.  
  958. /**
  959.  * Handles failure notification from the Ruby pull_attribute_tree function,
  960.  * reporting an error in the configuration panel to notify the user.
  961.  * @param {string} queryid The unique ID of the invocation that triggered
  962.  *   this callback.
  963.  */
  964. comp.handlePullAttributesFailure = function(queryid) {
  965.   comp.rootEntity = null;
  966. };
  967.  
  968. /**
  969.  * Handles success notification from the Ruby pull_attribute_tree function
  970.  * and triggers initial UI construction based on the selection attribute
  971.  * data provided by that routine.
  972.  * @param {string} queryid The unique ID of the invocation that triggered
  973.  *     this callback.
  974.  */
  975. comp.handlePullAttributesSuccess = function(queryid) {
  976.   comp.rootEntity = su.getRubyResponse(queryid);
  977. };
  978.  
  979. /**
  980.  * Pulls a list of comma-separated entity IDs from the current selection in
  981.  * the current active model. The resulting selection IDs are placed in the
  982.  * comp.selectionIds property for consumption as a comma-delimited string.
  983.  * @param {Object} request An object whose key/value pairs provide
  984.  *     parameters to the call.
  985.  */
  986. comp.pullSelectionIds = function(request){
  987.   // Default the success and failure handlers to update rootEntity data.
  988.   su.addIfAbsent(request, 'onsuccess', 'comp.handlePullSelectionIdsSuccess');
  989.   su.addIfAbsent(request, 'onfailure', 'comp.handlePullSelectionIdsFailure');
  990.   su.callRuby('pull_selection_ids', request);
  991. };
  992.  
  993. /**
  994.  * Handles failure notification from the Ruby pull_selection_ids function,
  995.  * reporting an error in the configuration panel to notify the user.
  996.  */
  997. comp.handlePullSelectionIdsFailure = function() {
  998.   comp.selectionIds = null;
  999. };
  1000.  
  1001. /**
  1002.  * Handles success notification from the Ruby pull_attribute_tree function
  1003.  * and triggers initial UI construction based on the selection attribute
  1004.  * data provided by that routine.
  1005.  * @param {string} queryid The unique ID of the invocation that triggered
  1006.  *     this callback.
  1007.  */
  1008. comp.handlePullSelectionIdsSuccess = function(queryid) {
  1009.   var obj;
  1010.   var ids;
  1011.  
  1012.   //  clear the selection IDs until we can recompute the list
  1013.   comp.selectionIds = null;
  1014.  
  1015.   if (su.notValid(obj = su.getRubyResponse(queryid))) {
  1016.     return;
  1017.   }
  1018.  
  1019.   if (su.notValid(ids = obj['selection_ids'])) {
  1020.     return;
  1021.   }
  1022.  
  1023.   comp.selectionIds = ids;
  1024. };
  1025.