From 003d5273073ffad163aade63c03eb2234ed99cbc Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 1 Apr 2016 13:50:03 -0400 Subject: [PATCH 01/20] Liberate context/ * ui/context/Context.js: Added * ui/context/DomContext.js: Added * ui/context/DynamicContext.js: Added * ui/context/RootDomContext.js: Added --- src/ui/context/Context.js | 34 +++ src/ui/context/DomContext.js | 347 +++++++++++++++++++++++++++++++ src/ui/context/DynamicContext.js | 64 ++++++ src/ui/context/RootDomContext.js | 63 ++++++ 4 files changed, 508 insertions(+) create mode 100644 src/ui/context/Context.js create mode 100644 src/ui/context/DomContext.js create mode 100644 src/ui/context/DynamicContext.js create mode 100644 src/ui/context/RootDomContext.js diff --git a/src/ui/context/Context.js b/src/ui/context/Context.js new file mode 100644 index 0000000..527a349 --- /dev/null +++ b/src/ui/context/Context.js @@ -0,0 +1,34 @@ +/** + * Field group context + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Interface = require( 'easejs' ).Interface; + + +/** + * A subset of a larger collection of fields that can be used to restrict + * operations for both convenience and (moreso) performance + */ +module.exports = Interface( 'Context', +{ + 'public getFieldByName': [ 'name', 'index', 'filter' ], + + 'public split': [ 'on' ] +} ); diff --git a/src/ui/context/DomContext.js b/src/ui/context/DomContext.js new file mode 100644 index 0000000..7bee154 --- /dev/null +++ b/src/ui/context/DomContext.js @@ -0,0 +1,347 @@ +/** + * DOM subset context + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Class = require( 'easejs' ).Class, + Context = require( './Context' ), + + EventEmitter = require( 'events' ).EventEmitter; + + +/** + * A subset of the DOM that can be used to restrict operations for both + * convenience and (moreso) performance + */ +module.exports = Class( 'DomContext' ) + .implement( Context ) + .extend( EventEmitter, +{ + /** + * Parent context, if any + * @type {DomContext} + */ + 'private _pcontext': null, + + /** + * DOM content for this particular context + * @type {HTMLElement} + */ + 'private _content': null, + + /** + * Parent to re-attach to + * @type {HtmlElement} + */ + 'private _contentParent': null, + + /** + * Factory used to produce DomFields + * @type {DomFieldFactory} + */ + 'private _fieldFactory': null, + + /** + * Cache of fields that have been looked up previously + * @type {Object} + */ + 'private _fieldCache': {}, + + /** + * Continuations to be invoked once attached to the DOM + * @type {Array.} + */ + 'private _attachq': [], + + /** + * Continuations to be invoked once detached from the DOM + * @type {Array.} + */ + 'private _detachq': [], + + + __construct: function( content, field_factory, pcontext, cache ) + { + // older browsers do not support HTMLElement, but we still want the type + // check for newer ones + if ( window.HTMLElement && !( content instanceof HTMLElement ) ) + { + throw TypeError( "Context content must be a valid HTMLElement" ); + } + else if ( !( this.verifyParentContext( pcontext ) ) ) + { + throw TypeError( "Invalid parent DomContext" ); + } + + this._content = content; + this._fieldFactory = field_factory; + this._pcontext = pcontext || null; + this._fieldCache = cache || {}; + }, + + + 'virtual protected verifyParentContext': function( context ) + { + return Class.isA( module.exports, context ); + }, + + + 'public split': function( on_id, c ) + { + var _self = this, + inst = _self.__inst; + + this._getElementById( on_id, function( element ) + { + // if the element could not be found, just return self + c( ( element ) + ? module.exports( + element, + _self._fieldFactory, + inst, + _self._fieldCache + ).on( 'error', function( e ) + { + // "bubble up" errors + _self.emit( 'error', e ); + } ) + + : inst + ); + } ); + + return this; + }, + + + 'public getFieldByName': function( name, index, filter ) + { + var result = this._fromCache( name, index ); + + if ( filter ) + { + throw Error( "TODO: filter" ); + } + + return result; + }, + + + 'private _getElementById': function( id, c ) + { + id = ''+id; + + if ( !id ) + { + c( null ); + return; + } + + // we cannot perform the highly performant getElementById() unless we + // are attached to the DOM + this.whenAttached( function() + { + c( document.getElementById( id ) ); + } ); + }, + + + 'private _fromCache': function( name, index, lookup ) + { + var data = ( + this._fieldCache[ name ] = this._fieldCache[ name ] || [] + ); + + // if already present within the cache, simply return it + if ( data[ index ] ) + { + return data[ index ]; + } + + // add to cache and return + var _self = this; + return data[ index ] = this._fieldFactory.create( + name, index, + + // this is intended to defer request of the root element until this + // context is attached to the DOM; this ensures that the requester + // can take advantage of features of the attached DOM such as + // getElementById() and defers initial DOM operations until the + // element is actually available on the DOM + function( c ) + { + // invoke the continuation as soon as we're attached to the DOM + _self.whenAttached( c ); + } + ).on( 'error', function( e ) + { + // forward errors + _self.emit( 'error', e ); + } ); + }, + + + /** + * Determines whether this context is currently attached to the DOM + * + * @return {boolean} true if attached to the DOM, otherwise false + */ + 'virtual public isAttached': function() + { + // we are attached if (a) our content node has a parent and (b) if our + // parent context is also attached + return !!this._content.parentElement && this._pcontext.isAttached(); + }, + + + /** + * Schedules a continunation to be invoked once the context becomes attached + * to the DOM + * + * If already attached, the continuation will be executed immediately + * (synchronously). + * + * @param {function()} c continuation to be invoked + * + * @return {DomContext} self + */ + 'public whenAttached': function( c ) + { + // invoke immediately if we're already attached + if ( this.isAttached() ) + { + c(); + return this; + } + + // queue continuation + var _self = this; + this._attachq.push( function() + { + // ensure that we're still attached to the DOM by the time this + // continuation is actually invoked + if ( !( _self.isAttached() ) ) + { + // tough luck; try again later + _self.whenAttached( c ); + return; + } + + c(); + } ); + + return this; + }, + + + 'public whenDetached': function( c ) + { + // invoke immediately if we're not attached + if ( this.isAttached() === false ) + { + c(); + return this; + } + + // queue the continuation + var _self = this; + this._detachc.push( function() + { + // ensure that we're still detached from the DOM + if ( _self.isAttached() ) + { + // tough luck; try again later + _self.whenDetached( c ); + return; + } + + c(); + } ); + + return this; + }, + + + 'virtual public attach': function( to ) + { + var _self = this; + + // if we are already attached to the DOM, then do nothing (note that we + // check the parent element of our content node because something could + // have detached the node from the DOM without us knowing) + if ( this._content.parentElement ) + { + return this; + } + + // default to the stored parent if they did not provide anything + to = ( to || this._contentParent ); + if ( !( Class.isA( HTMLElement, to ) ) ) + { + throw TypeError( "Cannot attach context to " + to.toString() ); + } + + // re-attach ourselves to our parent and dequeue the continuations only + // once our parent is attached (will execute immediately if we are + // already attached) + to.appendChild( this._content ); + this._pcontext.whenAttached( function() + { + _self._dequeue( _self._attachq ); + } ); + + return this; + }, + + + 'virtual public detach': function() + { + // do nothing if we are not attached to the DOM (note that we check the + // parent element of the content because something else could have + // re-attached our content node to the DOM without us knowing) + if ( !( this._content.parentElement ) ) + { + return this; + } + + // store the parent so that we know where to re-attach ourselves + this._contentParent = this._content.parentElement; + + // detach from the DOM and dequeue the conintinuations (we don't care if + // our parent is detached since we're still detached regardless) + this._contentParent.removeChild( this._content ); + this._dequeue( this._detachq ); + + return this; + }, + + + 'private _dequeue': function( q ) + { + // transfer continuation queue onto the JS timeout stack + var c, _self = this; + while ( c = q.shift() ) + { + // ensures that the continuations will be executed without locking + // up the browser; this is important, since these are DOM + // manipulations and therefore may be intensive! + setTimeout( c, 25 ); + } + } +} ); diff --git a/src/ui/context/DynamicContext.js b/src/ui/context/DynamicContext.js new file mode 100644 index 0000000..8a517fb --- /dev/null +++ b/src/ui/context/DynamicContext.js @@ -0,0 +1,64 @@ +/** + * Dynamic field context + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Class = require( 'easejs' ).Class, + Context = require( './Context' ); + + +/** + * Mutable Context + * + * This exists primarily to ease refactoring of old parts of the framework; + * it should not be preferred going forward. + */ +module.exports = Class( 'DynamicContext' ) + .implement( Context ) + .extend( +{ + /** + * Current context + * @type {Context} + */ + 'private _context': null, + + + __construct: function( initial ) + { + this.assign( initial ); + }, + + + 'public assign': function( context ) + { + if ( !( Class.isA( Context, context ) ) ) + { + throw TypeError( "Invalid context" ); + } + + this._context = context; + return this; + }, + + + 'public proxy getFieldByName': '_context', + + 'public proxy split': '_context' +} ); diff --git a/src/ui/context/RootDomContext.js b/src/ui/context/RootDomContext.js new file mode 100644 index 0000000..15574da --- /dev/null +++ b/src/ui/context/RootDomContext.js @@ -0,0 +1,63 @@ +/** + * DOM context representing document root + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +var Class = require( 'easejs' ).Class, + DomContext = require( './DomContext' ); + + +/** + * Intended to serve as the topmost context in a context tree + * + * Since all other DomContexts besides this one must have a parent, it may + * be useful to create other DomContext objects by split()'ing an instance + * of this class. + * + * The root context cannot be detached from the DOM. + */ +module.exports = Class( 'RootDomContext' ) + .extend( DomContext, +{ + 'override protected verifyParentContext': function( context ) + { + // we have no parent... :( + // (this class has Mommy/Daddy issues) + return true; + }, + + + 'override public isAttached': function() + { + // of course we are. + return true; + }, + + + 'override public attach': function( to ) + { + throw Error( "Cannot attach DOM root" ); + }, + + + 'override public detach': function( to ) + { + throw Error( "Cannot detach DOM root" ); + } +} ); From fea4e151ccb74cd74a9a3a5f8905c17ed1fc53c7 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Fri, 1 Apr 2016 14:10:42 -0400 Subject: [PATCH 02/20] Liberated archaic DOM element styler * src/ui/ElementStyler.js: Added * src/sort/MultiSort.js: Removed ElementStyler liberation todo * src/ui/field/DomFieldFactory.js: Same * src/ui/step/GeneralStepUi.js: Same * src/ui/step/StepUiBuilder.js: Same This existed when the framework was very long, and has managed to survive numerous refactoring attempts; it used to be a small class abstracting element styling, and it has had crap thrown into it ever since, partially due to time constraints. It needs to go away. --- src/data/UsStates.js | 101 +++ src/sort/MultiSort.js | 1 - src/ui/ElementStyler.js | 1314 +++++++++++++++++++++++++++++++ src/ui/field/DomFieldFactory.js | 5 - src/ui/step/GeneralStepUi.js | 1 - src/ui/step/StepUiBuilder.js | 1 - 6 files changed, 1415 insertions(+), 8 deletions(-) create mode 100644 src/data/UsStates.js create mode 100644 src/ui/ElementStyler.js diff --git a/src/data/UsStates.js b/src/data/UsStates.js new file mode 100644 index 0000000..4221bf6 --- /dev/null +++ b/src/data/UsStates.js @@ -0,0 +1,101 @@ +/** + * List of US states and codes + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +/** + * List of states and their codes + * @var {Object} + */ +var states = { + 'AL': 'Alabama', + 'AK': 'Alaska', + 'AZ': 'Arizona', + 'AR': 'Arkansas', + 'CA': 'California', + 'CO': 'Colorado', + 'CT': 'Connecticut', + 'DE': 'Delaware', + 'FL': 'Florida', + 'GA': 'Georgia', + 'HI': 'Hawaii', + 'ID': 'Idaho', + 'IL': 'Illinois', + 'IN': 'Indiana', + 'IA': 'Iowa', + 'KS': 'Kansas', + 'KY': 'Kentucky', + 'LA': 'Louisiana', + 'ME': 'Maine', + 'MD': 'Maryland', + 'MA': 'Massachusetts', + 'MI': 'Michigan', + 'MN': 'Minnesota', + 'MS': 'Mississippi', + 'MO': 'Missouri', + 'MT': 'Montana', + 'NE': 'Nebraska', + 'NV': 'Nevada', + 'NH': 'New Hampshire', + 'NJ': 'New Jersey', + 'NM': 'New Mexico', + 'NY': 'New York', + 'NC': 'North Carolina', + 'ND': 'North Dakota', + 'OH': 'Ohio', + 'OK': 'Oklahoma', + 'OR': 'Oregon', + 'PA': 'Pennsylvania', + 'RI': 'Rhode Island', + 'SC': 'South Carolina', + 'SD': 'South Dakota', + 'TN': 'Tennessee', + 'TX': 'Texas', + 'UT': 'Utah', + 'VT': 'Vermont', + 'VA': 'Virginia', + 'WA': 'Washington', + 'WV': 'West Virginia', + 'WI': 'Wisconsin', + 'WY': 'Wyoming', + + // nothing + '' : '', + 0: '' +}; + + +/** + * Gets the name of the state associated with the given code + * + * @param {string} code state abbr + * + * @return {string} name of state + */ +exports.getName = function( code ) +{ + var val = states[ code ]; + + // if the value was not found, return the code we were given (which likely + // makes no sense at all, since it's not a valid state) + return ( val === undefined ) + ? code + : val; +} diff --git a/src/sort/MultiSort.js b/src/sort/MultiSort.js index 37c7d35..943a981 100644 --- a/src/sort/MultiSort.js +++ b/src/sort/MultiSort.js @@ -22,7 +22,6 @@ * - References to "quote" should be replaced with generic terminology * representing a document. * - Dependencies need to be liberated: - * - ElementStyler; * - BucketDataValidator. * - Global references (e.g. jQuery) must be removed. * - Checkbox-specific logic must be extracted. diff --git a/src/ui/ElementStyler.js b/src/ui/ElementStyler.js new file mode 100644 index 0000000..10b20b5 --- /dev/null +++ b/src/ui/ElementStyler.js @@ -0,0 +1,1314 @@ +/** + * Archaic DOM element styler + * + * Copyright (C) 2016 LoVullo Associates, Inc. + * + * This file is part of liza. + * + * liza is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @needsLove + * - Everything! This class exists from when the framework was barely + * more than a few prototypes and has rotted ever since with little else + * but workarounds. + * @end needsLove + */ + +var State = require( '../data/UsStates' ), + Class = require( 'easejs' ).Class; + + +/** + * Styles DOM elements + * + * This class styles DOM elements with Dojo's Dijits (widgets). + * + * @return void + */ +module.exports = Class( 'ElementStyler', +{ + /** + * Stores element types as an associative array + * @type {Object.} + */ + elementTypes: {}, + + 'private _answerRefs': {}, + + /** + * Stores help text for elements + * @type {Object.} + */ + elementHelp: {}, + + /** + * Whether to show internal questions + * @type {boolean} + */ + _showInternal: false, + + /** + * Stores defaults for questions + * @type {Object} + */ + 'private _defaults': {}, + + /** + * Stores defaults for display only + * @type {Object} + */ + 'private _displayDefaults': {}, + + /** + * Contains data for select + * @type {Object.} + */ + 'private _selectData': {}, + + /** + * Selector context + * @type {jQuery} + */ + 'private _$context': null, + + + _answerStyles: { + 'currency': function( value ) + { + var pre = '$'; + + if ( !value ) + { + value = 0; + } + else if ( value < 0 ) + { + value *= -1; + pre = '-$'; + } + + var formatter = this._getAnswerStyler( 'number' ); + + return pre + formatter.call( this, ( +value ).toFixed( 2 ) ); + }, + + 'dollars': function( value ) + { + var formatter = this._getAnswerStyler( 'currency' ); + + return formatter.call( this, ( +value ).toFixed( 0 ) ); + }, + + 'float': function( value ) + { + if ( !value ) + { + value = 0; + } + + var formatter = this._getAnswerStyler( 'number' ); + + return formatter.call( this, ( +value ).toFixed( 2 ) ); + }, + + 'limit': function( value, _, default_val ) + { + value = ( value + '' ).replace( ',', '' ); + + + // if no value was given, be sure that we use the proper default + // value + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + // if the value is simply a string, return it + if ( /^[a-z_ -]+$/i.test( value ) ) + { + return value; + } + + // split on multiple limits (this will work fine if there's only + // one) + var data = ( ''+( value ) ).split( '/' ), + i = data.length; + + // simple comma addition (for thousands); XXX: this mess is an + // abomination + while ( i-- ) + { + data[ i ] = data[ i ] + .replace( /^.*(?:[0-9]{3}|[1-9]0{2})$/, function( number ) + { + var len = number.length, + ret = ''; + + // insert thousands separators into their proper + // positions + for ( var i = 0; i < len; i++ ) + { + ret += number.substr( i, 1 ); + + if ( ( ( len - i ) % 3 ) === 1 ) + { + ret += ','; + } + } + + // could handle this in the above loop, but this is more + // clear + return ret.replace( /,$/, '' ); + } ) + .replace( /^,?([0-9]{2,})$/, '$1,000' ) + .replace( /^,/, '' ); + } + + // re-join multiple limits + return data.join( '/' ); + }, + + 'multilimit': function( values, _, default_val ) + { + var limit = this._getAnswerStyler( 'limit' ); + + // if we're not an array, fall back to the normal limit styler + if ( typeof values !== 'object' ) + { + return limit.apply( this, arguments ); + } + + var formatted = [], + same = true; + + for ( var i in values ) + { + formatted.push( + limit.call( this, values[ i ], _, default_val ) + ); + + if ( i > 0 ) + { + same = ( same && formatted[ i ] === formatted[ i - 1 ] ); + } + } + + return ( same ) + ? formatted[ 0 ] + : formatted.join( '; ' ); + }, + + 'multitext': function( values, _, default_val ) + { + // for now + this._getAnswerStyler( 'multilimit' ).apply( + this, arguments + ); + }, + + 'deductible': function( value, _, default_val ) + { + // if no value was given, be sure that we use the proper default + // value + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + return value + ' Deductible'; + }, + + /* + * display as accepted, rejected or default if available + */ + 'acceptReject': function( value, _, default_val ) + { + // use the default if no value + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + var ret = value; + + if ( +value === 0 ) + { + ret = 'Rejected'; + } + else if ( +value === 1 ) + { + ret = 'Accepted'; + } + + return ret; + }, + + + 'includeExclude': function( value, _, default_val ) + { + // use the default if no value + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + return ( +value === 0 ) + ? 'Excluded' + : 'Included'; + }, + + + /* + * display as a limit, rejected or default if available + */ + 'limitReject': function( value, _, default_val ) + { + // use the default if no value + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + var limit = this._getAnswerStyler( 'number' ); + + if ( +value === 0 ) + { + ret = 'Rejected'; + } + else if ( +value === 1 ) + { + ret = 'Accepted'; + } + else + { + ret = limit.apply( this, arguments ); + } + + return ret; + }, + + + /** + * format thousands + */ + 'number': function( value ) + { + var str = value.toString().split( '.' ); + var len = str[0].length, + ret = ''; + + for ( var i = 0; i < len; i++ ) + { + ret += str[0].charAt( i ); + + if ( ( ( len - i ) % 3 ) === 1 ) + { + ret += ','; + } + } + + str[0] = ret.replace( /,$/, '' ); + + return str.join( '.' ) + }, + + 'state': function( value ) + { + return State.getName( value ); + }, + + /** + * Styles a no-yes answer + * + * A non-zero value is considered to be "Yes". An empty string (unless a + * default value is given) and "0" are considered to be "No". Default + * value is only returned if an empty string is provided. + * + * @param {string} value value to style + * @param {*} _ ignored + * @param {string_ default_val default value + * + * @return {string} styled answer + */ + 'noyes': function( value, _, default_val ) + { + // if a default value is provided, we will interpret an empty string + // as no value and return the default + if ( ( value === '' ) && default_val ) + { + return default_val; + } + + return ( value && ( value !== '0' ) ) + ? 'Yes' + : 'No'; + }, + + 'select': function( value, ref_id ) + { + var val = this._selectData[ref_id][value]; + + // return the string associated with the given value + // (the text for the option), or the given value if it does not + // exist + return ( val === undefined ) + ? value + : val; + }, + + 'manualDate': function( value ) + { + if( value.replace ) + { + return value.replace( + /^([0-9]{4})-([0-9]+)-([0-9]+)$/, + '$2/$3/$1' + ); + } + + return null; + }, + + 'date': function( value ) + { + var data = value.split( '-' ); + + // m/d/y + return data[1] + '/' + data[2] + '/' + data[0]; + }, + + 'dateTime': function( value ) + { + var ret_val = new Date( ( +value ) * 1000 ); + + // do not attempt to format if invalid date + if ( isNaN( ret_val.getDate() ) || value === '' ) + { + return ''; + } + + return ( ret_val.getMonth() + 1 ) + '/' + + ret_val.getDate() + '/' + + ret_val.getFullYear(); + }, + + percent: function( value ) + { + return value + '%'; + }, + + waitable: function( value ) + { + return value.replace( + /Please wait.../, + '
$&
' + ); + } + }, + + + __construct: function() + { + this._$context = jQuery; + }, + + + /** + * Returns the function to be used for the widget jQuery selector + * + * @return Function selector function + */ + getWidgetSelector: function() + { + return function( node, i, data ) + { + // name of the widget we're searching for + var name = data[3], + $node = $( node ); + + // if it's not a widget, bail + if ( !( $node.hasClass( 'widget' ) ) ) + { + return false; + } + + // if no name was given, then don't check for it (they just want to + // know if this is a widget) + if ( !( name ) ) + { + return true; + } + + // quick name check on self + if ( $node.attr( 'name' ) === ( name + '[]' ) ) + { + return true; + } + + // attempt to locate the element with the name we're looking for + var $named = $node.find( '[name="' + name + '[]"]' ); + if ( $named.length === 0 ) + { + return false; + } + + return true; + } + }, + + + getWidgetIdSelector: function() + { + return function( node, i, data ) + { + // name of the widget we're searching for + var id = data[3], + $node = $( node ); + + if ( !( $node.hasClass( 'widget' ) ) ) + { + return false; + } + + // if no name was given, then don't check for it (they just want to + // know if this is a widget) + if ( !( name ) ) + { + return true; + } + + // quick name check on self + if ( $node.attr( 'id' ) === id ) + { + return true; + } + + // attempt to locate the element with the name we're looking for + var $named = $node.find( '[id="' + id + '"]' ); + if ( $named.length === 0 ) + { + return false; + } + + return true; + } + }, + + + /** + * Applies the style to all DOM elements that are descendants of $content + * + * This method operates off of a very basic concept. It takes an array of + * data containing a jQuery selector and applies the associated attributes + * to the elements. These attributes are non-standard - that is, they are + * not valid HTML attributes. Dojo then parses out these attributes and + * generates the dijit HTML, replacing the existing element. + * + * @param jQuery $content parent element containing elements to style + * + * @return ElementStyler self to support method chaining + */ + apply: function( $content, parse ) + { + parse = ( parse === undefined ) ? true : !!parse; + + // if we're internal, show internal questions + if ( this._showInternal ) + { + $content.find( '.hidden.i' ).removeClass( 'hidden' ); + } + + return this; + }, + + + /** + * Called after the content is appended to the DOM for the first time + * + * This is used to do final processing for display. In this case, performing + * the actual styling based off of the previously set attributes. + * + * @return ElementStyler self to allow for method chaining + */ + postAppend: function( $content ) + { + return this; + }, + + + /** + * Retrieves the id associated with the given element + * + * The problem is that Dijits do not always use a single element. They'll + * often have multiple elements in order to achieve a certain effect. The id + * may be on a different element than the one that contains the correct name + * attribute. + * + * This method will attempt to find the id by checking first the given + * element, then its siblings, followed finally by its children. Having to + * check for siblings is slower than accessing directly, and having to go so + * far as to check the children is the slowest. It is uncommon to have to + * check the children, so that check is performed last. + * + * @param jQuery $element element to get id of + * + * @return String element id or undefined + */ + getIdFromElement: function( $element ) + { + // check to see if the given element has the id we're looking for + // first, otherwise the siblings/children most likely contain the id + // we're looking for + return $element.attr( 'widgetid' ) + || $element.attr( 'id' ) + || $element.siblings().filter( '[id]' ).attr( 'id' ) + || $element.children().filter( '[id]' ).attr( 'id' ); + }, + + + /** + * Gets the name from a given element + * + * This method is needed because the name attribute may exist on a different + * element than the one provided. + * + * @return String|undefined + */ + getNameFromElement: function( $element ) + { + if ( !( $element instanceof jQuery ) ) + { + // assume it's an id + $element = $( '#' + $element ); + } + + return $element.attr( 'name' ) + || $element.siblings().filter( '[name]' ).attr( 'name' ) + || $element.children().filter( '[name]' ).attr( 'name' ) + || ''; + }, + + + /** + * Returns the element with the name attribute + * + * This allows referencing the element that should be posted + * + * @return jQuery named element + */ + getNameElement: function( $element ) + { + // if the passed element has a name attribute, then no searching is + // needed + if ( $element.attr( 'name' ) ) + { + return $element; + } + + // attempt to find from siblings and children + return $element.siblings().filter( '[name]' ) + || $element.children().filter( '[name]' ) + }, + + + 'public setOptions': function( name, index, options, val, $context ) + { + var $element = this.getElementByName( name, index, null, $context ); + + // if the provided question is not a select, then we cannot add options + // to it---use the first index instead + if ( this._getElementType( name ) !== 'select' ) + { + $element.val( ( options[ 0 ] || { value: '' } ).value ); + return; + } + + // store the old value + val = val || $element.val(); + + $element.html(''); + + var answer_data = this._selectData[ name ] = {}; + for ( var item in options ) + { + var opt = options[ item ], + opt_value = opt.value === undefined || opt.value === null ? '' : opt.value; + + answer_data[ opt_value ] = opt.label; + + $element.append( + $( '