UNPKG

qiprofile

Version:

Quantitative Imaging Profile ( QiPr) web application

280 lines (257 loc) 10.5 kB
define ['angular', 'lodash', 'underscore.string', 'moment'], (ng, _, _s, moment) -> helpers = ng.module 'qiprofile.helpers', [] helpers.factory 'StringHelper', -> # Improves on underscore.string dasherize by converting each # sequence of two or more capital letters to lowercase without # dashes, e.g.: # StringHelper.dasherize('TNM') # returns 'tnm' rather than the underscore.string result '-t-n-m'. # # @param s the input string # @returns the lowercase dashed conversion dasherize: (s) -> # Prepares the given match for underscore.string dasherize. prep = (match) -> first = match[0] rest = match.substring(1) last = match[match.length - 1] if last == last.toUpperCase() first + rest.toLowerCase() else first + rest[...-2].toLowerCase() + rest[-2..] # Prepare the input string. prepped = s.replace(/[A-Z]{2,}.?/g, prep) # Delegate to underscore.string. sdashed = _s.dasherize(prepped) # Remove a bogus leading dash, if necessary. if sdashed[0] == '-' and s[0] != '-' sdashed.substring(1) else sdashed helpers.factory 'ObjectHelper', -> # Define here for reuse below. _sortValuesByKey = (obj) -> if obj? # The sorted keys. keys = _.keys(obj).sort() # Return the values sorted by key. (obj[key] for key in keys) else [] # @param key the property name # @param value the property value # @returns the {key, value} object associate: (key, value) -> obj = {} obj[key] = value obj # @param objects the delegate objects # @returns a new object with properties from the given objects delegate: (objects...) -> _.reduce(objects, _.defaults, {}) # Pretty prints the given object in a readable format. This function # handles cycles by substituting an elipsis ('...') if a referenced # object has already been visited. # # @param obj the object to print # @returns the string representation prettyPrint: (obj) -> # Adapted from # http://stackoverflow.com/questions/957537/how-can-i-print-a-javascript-object # and # //http://stackoverflow.com/questions/9382167/serializing-object-that-contains-cyclic-object-value. visited = [] replace = (key, val) -> elideCycles = (val) -> if _.isObject(val) if _.isFunction(val) or val in visited '...' else visited.push(val) val else val elideCycles(val) JSON.stringify(obj, replace, 2) # @param obj the associative object # @returns the concatenated values array sortValuesByKey: _sortValuesByKey # @param objects the objects from which to collect the values # @param accessor the associative object accessor callback or # property name # @returns the concatenated value objects collectValues: (objects, accessor) -> # Concatenates the given current associative object # values sorted by the associative keys to the given # accumulator array. # # @param accumulator the previous result array # @param current the current associative object # @returns the concatenated results array concatValues = (accumulator, current) -> values = _sortValuesByKey(current) accumulator.concat(values) # The associative objects. assocObjs = _.map(objects, accessor) # Accumulate the values. assocObjs.reduce(concatValues, []) # @param obj the first object to compare # @param other the second object to compare # @returns whether both objects have the same properties and # the corresponding property values are equal propertiesEqual: (obj, other) -> # Group by property name. props = _.unique(_.keys(obj).concat(_.keys(other))) pairs = ([obj[prop], other[prop]] for prop in props) # Return whether all value pairs are equal. _.every(pairs, (pair) -> pair[0] == pair[1]) # Aliases the source object properties which are not already # defined in the destination object. # # @param source the copy source object # @param dest the copy destination object # @param filter an optional function that filters the # properties to alias aliasProperties: (source, dest, filter=null) -> # @param prop the source property to alias in the destination defineAlias = (prop) -> Object.defineProperty dest, prop, enumerable: true get: -> source[prop] set: (val) -> source[prop] = val # The properties to alias. srcProps = Object.getOwnPropertyNames(source) destProps = Object.getOwnPropertyNames(dest) aliasProps = _.difference(srcProps, destProps) if filter aliasProps = aliasProps.filter(filter) # Make the virtual properties. # Note: each property must be defined in a call to the defineAlias # function. That function body cannot be inlined in the loop below # since CoffeeScript defines the iteration variable prop in a scope # outside of the loop, which in turn implies that the alias getter # and setter have a function closure which refers to a shared prop # variable which will resolve to the last iteration value. for prop in aliasProps defineAlias(prop) # Aliases the source object properties which are not already # defined in the destination object and satisfy thew following # conditions: # * the property name does not begin with an underscore (_) # or dollar sign ($) # * the source property value is not a function # # @param source the copy source object # @param dest the copy destination object aliasPublicDataProperties: (source, dest) -> # Filters the given property to select only non-functions # whose names do not begin with _ or $. filter = (prop) -> prop[0] not in '_$' and not _.isFunction(source[prop]) # Delegate to aliasProperties with the property filter. @aliasProperties(source, dest, filter) # Parses the JSON data into a Javascript object and creates # camelCase property aliases for underscore property names. # If the input data is an array Eve REST result, signified # by the presence of an _items array property, then this # method returns an array of Javascript objects, each of # which is recursively transformed by this function. # # @param data the REST JSON data # @returns the Javascript object fromJson: (data) -> # Aliases the given underscore property. # # @param obj the input object to modify # @param property the input property to alias camelizeProperty = (obj, property) -> # The camelCase alias. alias = property[0] + _s.camelize(property.substring(1)) # If the camelCase alias is not yet defined, then # create the new property. if _.isObject(obj) and not obj.hasOwnProperty(alias) # Make the camelCase property. Object.defineProperty obj, alias, enumerable: true get: -> obj[property] set: (value) -> obj[property] = value # Preserve the underscore property, but remove # it from the enumerable list. Object.defineProperty obj, property, enumerable: false value: obj[property] writable: true # Aliases underscore properties with camelCase properties. # # @param obj the input object to modify # @returns the input object camelizeProperties = (obj, visited=[]) -> # Add camelCase aliases to underscore properties. # Only unvisited plain objects are aliased. if _.isObject(obj) and obj not in visited # Mark the object as visited. visited.push(obj) for key, val of obj # If the key has a non-leading underscore, then # camelize the underscore property. if key.lastIndexOf('_') > 0 camelizeProperty(obj, key) # Recurse. if _.isArray(val) camelizeProperties(item, visited) for item in val else camelizeProperties(val, visited) # Return the input object. obj # End of camelizeProperties. # The initial parsed Javascript object. obj = ng.fromJson(data) # If the object is a REST array with _items, then return # the camelized items. Otherwise, returns the camelized # object. if obj.hasOwnProperty('_items') visited = [] camelizeProperties(item, visited) for item in obj._items else camelizeProperties(obj) helpers.factory 'DateHelper', ['ObjectHelper', (ObjectHelper) -> # Converts the input to a moment. The input can be a date string, # e.g. '07 Sep 1986', or the number of milliseconds since # the Unix Epoch (Jan 1 1970 12AM UTC). # # A string input must contain the date in the format # DD MMM YYYY, e.g. 'Tue, 03 Feb 2012 00:00:00 GMT'. # Otherwise, an error is thrown. Note that the time in # the example input is ignored. # # @param date the date integer or string # @returns the parsed moment date, or the input date # if the input date is undefined or null asMoment: (date) -> if date? if _.isString(date) match = /(\d{2}) ([A-Z][a-z]{2}) (\d{4})/.exec(date) if match moment(match[1..3].join(' '), 'DD MMM YYYY') else throw new Error("Input string does not contain a date" + " formatted as DD MMM YYYY: #{ date }") else moment(date) else date # Anonymizes the given date by changing the month and day to # July 7. # # @param date the date to anonymize # @returns the new anonymized date, or undefined # if the input date is undefined or null anonymize: (date) -> # The quirky Javascript Date month is zero-based but the # day of month ('date' in Javascript Date's equally quirky # naming convention) is one-based. if date? moment([date.year(), 6, 7]) else date ]