123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- "use strict";
- // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
- // Secretary of Defense (Personnel & Readiness).
- //
- // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- // in compliance with the License. You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software distributed under the License
- // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- // or implied. See the License for the specific language governing permissions and limitations under
- // the License.
- /// Kernel utility functions.
- ///
- /// @module vwf/utility
- define( [ "module",
- "vwf/utility/xpath",
- "vwf/utility/color",
- "vwf/utility/coordinates"
- ], function( module,
- xpath,
- color,
- coordinates
- ) {
- var exports = {
- // -- transform ----------------------------------------------------------------------------
- /// Recursively transform an arbitrary object using the provided transformation function.
- /// Containers are duplicated where necessary so that the original object and any contained
- /// objects are not modified. Unchanged objects will be referenced directly in the result.
- ///
- /// @param {Object} object
- /// The object to transform. `Object` and `Array` contents are recursively transformed
- /// using the same transformation function.
- /// @param {Function} transformation
- /// The transformation function: `function( object, names, depth, finished ) { return object }`
- /// Call `finished()` from the transformation function to stop recursion for the current
- /// object.
- /// @param {(Number|String)[]} [names]
- /// Array of the names or indexes that refer to `object` in its container and in its
- /// containers' containers, parent first (recursive calls only).
- /// @param {Number} [depth]
- /// Recursion depth (recursive calls only).
- ///
- /// @returns {Object}
- /// The transformed object.
- transform: function( object, transformation /* ( object, names, depth, finished ) */, names, depth ) {
- names = names || [];
- depth = depth || 0;
- var finished = false, item;
- var result = object = transformation( object, names, depth, function() { finished = true } );
- if ( typeof object === "object" && object !== null && ! finished ) {
- if ( object instanceof Array ) {
- // Recursively transform the elements if the object is an Array.
- for ( var index = 0; index < object.length; index++ ) {
- if ( ( item = this.transform( object[index], transformation,
- [ index ].concat( names ), depth + 1 ) ) !== object[index] ) {
- // If the item changed, and it's the first change in the array, then
- // duplicate the array.
- if ( result === object ) {
- result = [].concat( object ); // shallow copy into a new Array
- }
- // Assign the transformed item.
- result[index] = item;
- }
- }
- } else {
- // Recursively transform the properties if the object is an Object.
- Object.keys( object ).forEach( function( key ) {
- if ( ( item = this.transform( object[key], transformation,
- [ key ].concat( names ), depth + 1 ) ) !== object[key] ) {
- // If the item changed, and it's the first change in the object, then
- // duplicate the object.
- if ( result === object ) {
- result = {};
- Object.keys( object ).forEach( function( k ) {
- result[ k ] = object[ k ]; // shallow copy into a new Object
- } );
- // Also copy the non-enumerable `length` property for an `arguments`
- // object.
- var lengthDescriptor = Object.getOwnPropertyDescriptor( object, "length" );
- if ( lengthDescriptor && ! lengthDescriptor.enumerable ) {
- Object.defineProperty( result, "length", lengthDescriptor );
- }
- }
- // Assign the transformed item.
- result[key] = item;
- }
- }, this );
- }
- }
- return result;
- },
- // -- transforms ---------------------------------------------------------------------------
- /// Transformation functions for vwf.utility.transform. Invoke these as:
- ///
- /// utility.transform( object, utility.transforms.*transform* )
- ///
- /// to apply the transform utility.transforms.*transform* to object.
- transforms: {
- // -- transit --------------------------------------------------------------------------
- /// A vwf.utility.transform transformation function to convert an object for proper JSON
- /// serialization. Array-like objects are converted to actual Arrays. All other objects
- /// are unchanged. Invoke as: utility.transform( object, utility.transforms.transit ).
- ///
- /// @param {Object} object
- /// The object being transformed or one of its descendants.
- ///
- /// @returns {Object}
- /// An Array-like converted to an Array, or *object*.
- transit: function( object ) {
- // Determine if an object is Array-like (but not an Array) to identify objects that
- // need to be converted to Arrays for normalization.
- function isArraylike( candidate ) {
- var arraylike = false;
- // Filter with typeof and instanceof since they're much faster than toString().
- // Then check for typed arrays (Int8Array, Uint8Array, ...) or the Arguments
- // object using the type string.
- if ( typeof candidate == "object" && candidate != null && ! ( candidate instanceof Array ) ) {
- var typeString = Object.prototype.toString.call( candidate ) // eg, "[object *Type*]"
- arraylike = ( typeString.slice( -6 ) == "Array]" || typeString == "[object Arguments]" );
- }
- return arraylike;
- };
- // Convert typed arrays to regular arrays.
- return isArraylike( object ) ?
- Array.prototype.slice.call( object ) : object;
- },
- // -- hash -----------------------------------------------------------------------------
- /// A vwf.utility.transform transformation function to normalize an object so that it
- /// can be serialized and hashed with consistent results. Numeric precision is reduced
- /// to match the precision retained by the reflector. Non-Array objects are reordered so
- /// that their keys are in alphabetic order. Other objects are unchanged. Invoke as:
- /// utility.transform( object, utility.transforms.hash ).
- ///
- /// @param {Object} object
- /// The object being transformed or one of its descendants.
- ///
- /// @returns {Object}
- /// A reduced-precision number, an Object with alphabetic keys, or *object*.
- hash: function( object ) {
- // Apply the `transit` transform first to convert Array-likes into Arrays.
- object = exports.transforms.transit( object );
- // Reduce numeric precision slightly to match what passes through the reflector.
- if ( typeof object == "number" ) {
- return Number( object.toPrecision(15) );
- }
- // Order objects alphabetically.
- else if ( typeof object == "object" && object != null && ! ( object instanceof Array ) ) {
- var ordered = {};
- Object.keys( object ).sort().forEach( function( key ) {
- ordered[key] = object[key];
- } );
- return ordered;
- }
- return object;
- },
- },
- // -- exceptionMessage ---------------------------------------------------------------------
- /// Format the stack trace for readability.
- ///
- /// @param {Error} error
- /// An Error object, generally provided by a catch statement.
- ///
- /// @returns {String}
- /// An error message that may be written to the console or a log.
- exceptionMessage: function( error ) {
- // https://github.com/eriwen/javascript-stacktrace sniffs the browser type from the
- // exception this way.
- if ( error.arguments && error.stack ) { // Chrome
- return "\n " + error.stack;
- } else if ( window && window.opera ) { // Opera
- return error.toString();
- } else if ( error.stack ) { // Firefox
- return "\n " + error.toString() + "\n" + // somewhat like Chrome's
- error.stack.replace( /^/mg, " " );
- } else { // default
- return error.toString();
- }
- },
- // -- resolveURI ---------------------------------------------------------------------------
- /// Convert a relative URI to an absolute URI.
- ///
- /// @param {String} uri
- /// The URI to resolve. If uri is relative, it will be interpreted with respect to
- /// baseURI, or with respect to the document if baseURI is not provided.
- /// @param {String} [baseURI]
- /// An optional URI that provides the reference for uri. If baseURI is not provided, uri
- /// will be interpreted with respect to the document. If baseURI is relative, it will be
- /// interpreted with respect to the document before resolving uri.
- ///
- /// @returns {String}
- /// uri as an absolute URI.
- resolveURI: function( uri, baseURI ) {
- var doc = document;
- if ( baseURI ) {
- // Create a temporary document anchored at baseURI.
- var doc = document.implementation.createHTMLDocument( "resolveURI" );
- // Insert a <base/> with the reference URI: <head><base href=*baseURI*/></head>.
- var base = doc.createElement( "base" );
- base.href = this.resolveURI( baseURI ); // resolve wrt the document
- var head = doc.getElementsByTagName( "head" )[0];
- head.appendChild( base );
- }
- // Create an <a/> and resolve the URI.
- var a = doc.createElement( "a" );
- a.href = uri;
- return a.href;
- },
- // -- merge --------------------------------------------------------------------------------
- /// Merge fields from the `source` objects into `target`.
- merge: function( target /* [, source1 [, source2 ... ] ] */ ) {
- for ( var index = 1; index < arguments.length; index++ ) {
- var source = arguments[index];
- Object.keys( source ).forEach( function( key ) {
- if ( source[key] !== undefined ) {
- target[key] = source[key];
- }
- } );
- }
- return target;
- },
- validObject: function( obj ) {
- var objType = ( {} ).toString.call( obj ).match( /\s([a-zA-Z]+)/ )[ 1 ].toLowerCase();
- return ( objType != 'null' && objType != 'undefined' );
- },
- hasFileType: function( value ) {
- return ( this.fileType( value ) !== undefined )
- },
- fileType: function( filename ) {
- var fileFormat = undefined;
- var temp = filename.split( '.' );
- if ( temp.length > 1 ) {
- fileFormat = temp.pop();
- if ( fileFormat.length > 5 ) {
- fileFormat = undefined;
- }
- }
- return fileFormat;
- },
- ifPrototypeGetId: function( appID, prototypes, nodeID, childID ) {
- var prototypeID = undefined;
- if ( ( nodeID == 0 && childID != appID ) || prototypes[ nodeID ] !== undefined ) {
- if ( nodeID != 0 || childID != appID ) {
- prototypeID = nodeID ? nodeID : childID;
- if ( prototypes[ prototypeID ] !== undefined ) {
- prototypeID = childID;
- }
- return prototypeID;
- }
- }
- return undefined;
- },
- isString: function( s ) {
- return ( typeof( s ) === 'string' || s instanceof String );
- },
- isFunction: function( obj ) {
- return ( typeof obj === 'function' || obj instanceof Function );
- },
- // -- xpath --------------------------------------------------------------------------------
- /// XPath resolution functions.
- xpath: xpath,
- // -- color --------------------------------------------------------------------------------
- /// HTML/CSS color conversion functions.
- color: color,
- // -- coordinates --------------------------------------------------------------------------
- /// DOM element coordinate conversion functions.
- coordinates: coordinates,
- };
- return exports;
- } );
|