utility.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. "use strict";
  2. // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
  3. // Secretary of Defense (Personnel & Readiness).
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  6. // in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed under the License
  11. // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  12. // or implied. See the License for the specific language governing permissions and limitations under
  13. // the License.
  14. /// Kernel utility functions.
  15. ///
  16. /// @module vwf/utility
  17. define( [ "module",
  18. "vwf/utility/xpath",
  19. "vwf/utility/color",
  20. "vwf/utility/coordinates"
  21. ], function( module,
  22. xpath,
  23. color,
  24. coordinates
  25. ) {
  26. var exports = {
  27. // -- transform ----------------------------------------------------------------------------
  28. /// Recursively transform an arbitrary object using the provided transformation function.
  29. /// Containers are duplicated where necessary so that the original object and any contained
  30. /// objects are not modified. Unchanged objects will be referenced directly in the result.
  31. ///
  32. /// @param {Object} object
  33. /// The object to transform. `Object` and `Array` contents are recursively transformed
  34. /// using the same transformation function.
  35. /// @param {Function} transformation
  36. /// The transformation function: `function( object, names, depth, finished ) { return object }`
  37. /// Call `finished()` from the transformation function to stop recursion for the current
  38. /// object.
  39. /// @param {(Number|String)[]} [names]
  40. /// Array of the names or indexes that refer to `object` in its container and in its
  41. /// containers' containers, parent first (recursive calls only).
  42. /// @param {Number} [depth]
  43. /// Recursion depth (recursive calls only).
  44. ///
  45. /// @returns {Object}
  46. /// The transformed object.
  47. transform: function( object, transformation /* ( object, names, depth, finished ) */, names, depth ) {
  48. names = names || [];
  49. depth = depth || 0;
  50. var finished = false, item;
  51. var result = object = transformation( object, names, depth, function() { finished = true } );
  52. if ( typeof object === "object" && object !== null && ! finished ) {
  53. if ( object instanceof Array ) {
  54. // Recursively transform the elements if the object is an Array.
  55. for ( var index = 0; index < object.length; index++ ) {
  56. if ( ( item = this.transform( object[index], transformation,
  57. [ index ].concat( names ), depth + 1 ) ) !== object[index] ) {
  58. // If the item changed, and it's the first change in the array, then
  59. // duplicate the array.
  60. if ( result === object ) {
  61. result = [].concat( object ); // shallow copy into a new Array
  62. }
  63. // Assign the transformed item.
  64. result[index] = item;
  65. }
  66. }
  67. } else {
  68. // Recursively transform the properties if the object is an Object.
  69. Object.keys( object ).forEach( function( key ) {
  70. if ( ( item = this.transform( object[key], transformation,
  71. [ key ].concat( names ), depth + 1 ) ) !== object[key] ) {
  72. // If the item changed, and it's the first change in the object, then
  73. // duplicate the object.
  74. if ( result === object ) {
  75. result = {};
  76. Object.keys( object ).forEach( function( k ) {
  77. result[ k ] = object[ k ]; // shallow copy into a new Object
  78. } );
  79. // Also copy the non-enumerable `length` property for an `arguments`
  80. // object.
  81. var lengthDescriptor = Object.getOwnPropertyDescriptor( object, "length" );
  82. if ( lengthDescriptor && ! lengthDescriptor.enumerable ) {
  83. Object.defineProperty( result, "length", lengthDescriptor );
  84. }
  85. }
  86. // Assign the transformed item.
  87. result[key] = item;
  88. }
  89. }, this );
  90. }
  91. }
  92. return result;
  93. },
  94. // -- transforms ---------------------------------------------------------------------------
  95. /// Transformation functions for vwf.utility.transform. Invoke these as:
  96. ///
  97. /// utility.transform( object, utility.transforms.*transform* )
  98. ///
  99. /// to apply the transform utility.transforms.*transform* to object.
  100. transforms: {
  101. // -- transit --------------------------------------------------------------------------
  102. /// A vwf.utility.transform transformation function to convert an object for proper JSON
  103. /// serialization. Array-like objects are converted to actual Arrays. All other objects
  104. /// are unchanged. Invoke as: utility.transform( object, utility.transforms.transit ).
  105. ///
  106. /// @param {Object} object
  107. /// The object being transformed or one of its descendants.
  108. ///
  109. /// @returns {Object}
  110. /// An Array-like converted to an Array, or *object*.
  111. transit: function( object ) {
  112. // Determine if an object is Array-like (but not an Array) to identify objects that
  113. // need to be converted to Arrays for normalization.
  114. function isArraylike( candidate ) {
  115. var arraylike = false;
  116. // Filter with typeof and instanceof since they're much faster than toString().
  117. // Then check for typed arrays (Int8Array, Uint8Array, ...) or the Arguments
  118. // object using the type string.
  119. if ( typeof candidate == "object" && candidate != null && ! ( candidate instanceof Array ) ) {
  120. var typeString = Object.prototype.toString.call( candidate ) // eg, "[object *Type*]"
  121. arraylike = ( typeString.slice( -6 ) == "Array]" || typeString == "[object Arguments]" );
  122. }
  123. return arraylike;
  124. };
  125. // Convert typed arrays to regular arrays.
  126. return isArraylike( object ) ?
  127. Array.prototype.slice.call( object ) : object;
  128. },
  129. // -- hash -----------------------------------------------------------------------------
  130. /// A vwf.utility.transform transformation function to normalize an object so that it
  131. /// can be serialized and hashed with consistent results. Numeric precision is reduced
  132. /// to match the precision retained by the reflector. Non-Array objects are reordered so
  133. /// that their keys are in alphabetic order. Other objects are unchanged. Invoke as:
  134. /// utility.transform( object, utility.transforms.hash ).
  135. ///
  136. /// @param {Object} object
  137. /// The object being transformed or one of its descendants.
  138. ///
  139. /// @returns {Object}
  140. /// A reduced-precision number, an Object with alphabetic keys, or *object*.
  141. hash: function( object ) {
  142. // Apply the `transit` transform first to convert Array-likes into Arrays.
  143. object = exports.transforms.transit( object );
  144. // Reduce numeric precision slightly to match what passes through the reflector.
  145. if ( typeof object == "number" ) {
  146. return Number( object.toPrecision(15) );
  147. }
  148. // Order objects alphabetically.
  149. else if ( typeof object == "object" && object != null && ! ( object instanceof Array ) ) {
  150. var ordered = {};
  151. Object.keys( object ).sort().forEach( function( key ) {
  152. ordered[key] = object[key];
  153. } );
  154. return ordered;
  155. }
  156. return object;
  157. },
  158. },
  159. // -- exceptionMessage ---------------------------------------------------------------------
  160. /// Format the stack trace for readability.
  161. ///
  162. /// @param {Error} error
  163. /// An Error object, generally provided by a catch statement.
  164. ///
  165. /// @returns {String}
  166. /// An error message that may be written to the console or a log.
  167. exceptionMessage: function( error ) {
  168. // https://github.com/eriwen/javascript-stacktrace sniffs the browser type from the
  169. // exception this way.
  170. if ( error.arguments && error.stack ) { // Chrome
  171. return "\n " + error.stack;
  172. } else if ( window && window.opera ) { // Opera
  173. return error.toString();
  174. } else if ( error.stack ) { // Firefox
  175. return "\n " + error.toString() + "\n" + // somewhat like Chrome's
  176. error.stack.replace( /^/mg, " " );
  177. } else { // default
  178. return error.toString();
  179. }
  180. },
  181. // -- resolveURI ---------------------------------------------------------------------------
  182. /// Convert a relative URI to an absolute URI.
  183. ///
  184. /// @param {String} uri
  185. /// The URI to resolve. If uri is relative, it will be interpreted with respect to
  186. /// baseURI, or with respect to the document if baseURI is not provided.
  187. /// @param {String} [baseURI]
  188. /// An optional URI that provides the reference for uri. If baseURI is not provided, uri
  189. /// will be interpreted with respect to the document. If baseURI is relative, it will be
  190. /// interpreted with respect to the document before resolving uri.
  191. ///
  192. /// @returns {String}
  193. /// uri as an absolute URI.
  194. resolveURI: function( uri, baseURI ) {
  195. var doc = document;
  196. if ( baseURI ) {
  197. // Create a temporary document anchored at baseURI.
  198. var doc = document.implementation.createHTMLDocument( "resolveURI" );
  199. // Insert a <base/> with the reference URI: <head><base href=*baseURI*/></head>.
  200. var base = doc.createElement( "base" );
  201. base.href = this.resolveURI( baseURI ); // resolve wrt the document
  202. var head = doc.getElementsByTagName( "head" )[0];
  203. head.appendChild( base );
  204. }
  205. // Create an <a/> and resolve the URI.
  206. var a = doc.createElement( "a" );
  207. a.href = uri;
  208. return a.href;
  209. },
  210. // -- merge --------------------------------------------------------------------------------
  211. /// Merge fields from the `source` objects into `target`.
  212. merge: function( target /* [, source1 [, source2 ... ] ] */ ) {
  213. for ( var index = 1; index < arguments.length; index++ ) {
  214. var source = arguments[index];
  215. Object.keys( source ).forEach( function( key ) {
  216. if ( source[key] !== undefined ) {
  217. target[key] = source[key];
  218. }
  219. } );
  220. }
  221. return target;
  222. },
  223. validObject: function( obj ) {
  224. var objType = ( {} ).toString.call( obj ).match( /\s([a-zA-Z]+)/ )[ 1 ].toLowerCase();
  225. return ( objType != 'null' && objType != 'undefined' );
  226. },
  227. hasFileType: function( value ) {
  228. return ( this.fileType( value ) !== undefined )
  229. },
  230. fileType: function( filename ) {
  231. var fileFormat = undefined;
  232. var temp = filename.split( '.' );
  233. if ( temp.length > 1 ) {
  234. fileFormat = temp.pop();
  235. if ( fileFormat.length > 5 ) {
  236. fileFormat = undefined;
  237. }
  238. }
  239. return fileFormat;
  240. },
  241. ifPrototypeGetId: function( appID, prototypes, nodeID, childID ) {
  242. var prototypeID = undefined;
  243. if ( ( nodeID == 0 && childID != appID ) || prototypes[ nodeID ] !== undefined ) {
  244. if ( nodeID != 0 || childID != appID ) {
  245. prototypeID = nodeID ? nodeID : childID;
  246. if ( prototypes[ prototypeID ] !== undefined ) {
  247. prototypeID = childID;
  248. }
  249. return prototypeID;
  250. }
  251. }
  252. return undefined;
  253. },
  254. isString: function( s ) {
  255. return ( typeof( s ) === 'string' || s instanceof String );
  256. },
  257. isFunction: function( obj ) {
  258. return ( typeof obj === 'function' || obj instanceof Function );
  259. },
  260. // -- xpath --------------------------------------------------------------------------------
  261. /// XPath resolution functions.
  262. xpath: xpath,
  263. // -- color --------------------------------------------------------------------------------
  264. /// HTML/CSS color conversion functions.
  265. color: color,
  266. // -- coordinates --------------------------------------------------------------------------
  267. /// DOM element coordinate conversion functions.
  268. coordinates: coordinates,
  269. };
  270. return exports;
  271. } );