1
0
Fork 0

GroupUi show/hide based on liza DOM API

This isn't entirely complete---some groups don't make use of it (namely
TableGroupUi).  But it's a start.
master
Mike Gerwitz 2016-04-05 00:15:34 -04:00
commit 31f67b6719
22 changed files with 3278 additions and 145 deletions

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* 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;
}

View File

@ -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.

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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' ]
} );

View File

@ -0,0 +1,350 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/
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.<function()>}
*/
'private _attachq': [],
/**
* Continuations to be invoked once detached from the DOM
* @type {Array.<function()>}
*/
'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;
},
/**
* @todo: rename me to unqueue; dequeue is a data structure
*/
'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 );
}
}
} );

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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'
} );

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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" );
}
} );

View File

@ -37,13 +37,19 @@ module.exports = Class( 'DomField' )
'private _element': null,
/**
* Function used to query for element
* @type {function(function(HTMLElement))}
*/
'private _query': null,
'private _idPrefix': 'q_',
/**
* Currently active styles
* @type {Object}
* Cached immediate parent
* @type {HTMLElement}
*/
'private _styles': {},
'private _parent': null,
__construct: function( field, element )
@ -53,8 +59,8 @@ module.exports = Class( 'DomField' )
throw TypeError( "Invalid field provided" );
}
this._field = field;
this._element = element;
this._field = field;
this._query = element;
},
@ -62,77 +68,117 @@ module.exports = Class( 'DomField' )
'public proxy getIndex': '_field',
/**
* Attempt to retrieve element associated with field
*
* CALLBACK will be invoked with the element, if found. The DOM is
* always queried in case the element associated with this field
* changes, but if the element is not found, then it is assumed to be
* detached and the last known element is returned.
*
* @param {function(HTMLElement)} callback element callback
*
* @return {undefined}
*/
'private _getElement': function( callback )
{
// if the provided root is a function, then it should be lazily laoded
if ( this._element === null )
if ( this._query === null )
{
// if the element is null, then we have some serious problems; do
// not even invoke the callback
return;
}
else if ( typeof this._element === 'function' )
this.queryElement( callback );
},
/**
* Locate field element on the DOM, or return last known element
*
* @todo We used to cache the element in memory, period, but we have no
* reliable way to clear it from memory in older versions of
* browsers. For browsers that support DOM mutator events, we should
* use them.
*
* @param {function(HTMLElement)} callback element callback
*
* @return {undefined}
*/
'protected queryElement': function( callback )
{
var _self = this,
orig_query = this._query;
// any further requests for this element should be queued rather
// than resulting in a thundering herd toward the DOM (imporant: do
// this *before* invoking the function, since it may be synchronous)
var queue = [];
this._query = function( c )
{
var _self = this,
f = this._element;
queue.push( c );
};
// any further requests for this element should be queued rather
// than resulting in a thundering herd toward the DOM (imporant: do
// this *before* invoking the function, since it may be synchronous)
var queue = [];
this._element = function( c )
// attempt to retrieve our element from the DOM
orig_query( function( element )
{
var new_element = element || _self._element;
if ( !new_element )
{
queue.push( c );
};
_self._element = null;
_self.emit( 'error', Error(
"Cannot locate DOM element for field " +
_self.getName() + "[" + _self.getIndex() + "]"
) );
// attempt to retrieve our element from the DOM
f( function( element )
// do not even finish; this shit is for real.
return;
}
if ( new_element !== _self._element )
{
if ( !element )
{
_self._element = null;
_self.emit( 'error', Error(
"Cannot locate DOM element for field " +
_self.getName() + "[" + _self.getIndex() + "]"
) );
_self.updateElement( new_element );
}
// do not even finish; this shit is for real.
return;
}
// restore original query
_self._query = orig_query;
_self._element = element;
callback( element );
callback( new_element );
// if we have any queued requests, process them when we're not
// busy
var c;
while ( c = queue.shift() )
// if we have any queued requests, process them when we're not
// busy
var c;
while ( c = queue.shift() )
{
( function( c )
{
setTimeout( function()
{
// return the element to the queued callback
c( element );
}, 25 );
}
} );
return;
}
// we already have the element; immediately return it
callback( this._element );
} )( c );
}
} );
},
'private _hasStyle': function( style )
/**
* Update cached element
*
* The parent of NEW_ELEMENT is cached so that it can be reattached to
* the DOM after a detach.
*
* @param {HTMLElement} new_element new field element
*
* @return {undefined}
*/
'protected updateElement': function( new_element )
{
return !!this._styles[ style.getId() ];
},
'private _flagStyle': function( style, flag )
{
this._styles[ style.getId() ] = !!flag;
this._element = new_element;
this._parent = new_element.parentElement;
},
@ -140,23 +186,19 @@ module.exports = Class( 'DomField' )
{
var _self = this;
// if we already have this style applied, then ignore this request
if ( this._hasStyle( style ) )
{
return this;
}
// all remaining arguments should be passed to the style
var sargs = Array.prototype.slice.call( arguments, 1 );
// flag style immediately to ensure we do not queue multiple application
// requests
this._flagStyle( style, true );
// wait for our element to become available on the DOM and perform the
// styling
this._getElement( function( root )
{
// if we already have this style applied, then ignore this request
if ( style.isApplied( _self.__inst, root ) )
{
return;
}
style.applyStyle.apply(
style,
[ _self.__inst, root, _self.getContainingRow() ].concat( sargs )
@ -171,18 +213,14 @@ module.exports = Class( 'DomField' )
{
var _self = this;
// if this style is not applied, then do nothing
if ( !( this._hasStyle( style ) ) )
{
return this;
}
// immediately flag style to ensure that we do not queue multiple
// revocation requests
this._flagStyle( style, false );
this._getElement( function( root )
{
// if we already have this style applied, then ignore this request
if ( !style.isApplied( _self.__inst, root ) )
{
return;
}
style.revokeStyle( _self.__inst, root, _self.getContainingRow() );
} );
@ -222,22 +260,46 @@ module.exports = Class( 'DomField' )
},
// TODO: move me
// TODO: move me; too many odd exceptions; standardize
'protected getContainingRow': function()
{
var dd = this.getParent( this._element, 'dd' ),
var node_name = this._element.nodeName.toUpperCase();
if ( ( node_name === 'DT' ) || ( node_name === 'DD' ) )
{
return [ this._element ];
}
var dd = this.getParent( 'dd' ),
dt = ( dd ) ? this.getPrecedingSibling( dd, 'dt' ) : null;
return ( dt )
? [ dd, dt ]
: [ this.getParent( this._element ) ];
: [ this.getParent() ];
},
'protected getParent': function( element, type )
'public getParent': function( type )
{
return this.getElementParent( this._element, type );
},
'protected getElementParent': function( element, type )
{
var parent = element.parentElement;
if ( element === this._element )
{
parent = parent || this._parent;
// update parent reference if it's since changed
if ( this._parent !== parent )
{
this._parent = parent;
}
}
if ( parent === null )
{
return null;
@ -247,14 +309,14 @@ module.exports = Class( 'DomField' )
return parent;
}
// nodeName is in caps
if ( type.toUpperCase() === parent.nodeName )
// nodeName might not be in caps
if ( type.toUpperCase() === parent.nodeName.toUpperCase() )
{
return parent;
}
// otherwise, keep looking
return this.getParent( parent, type );
return this.getElementParent( parent, type );
},

View File

@ -1,7 +1,7 @@
/**
* Creates DomField
*
* Copyright (C) 2015 LoVullo Associates, Inc.
* Copyright (C) 2015, 2016 LoVullo Associates, Inc.
*
* This file is part of liza.
*
@ -17,11 +17,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @needsLove
* - Dependencies need to be liberated:
* - ElementStyler.
* @end needsLove
*/
var Class = require( 'easejs' ).Class,
@ -85,7 +80,7 @@ module.exports = Class( 'DomFieldFactory',
function c()
{
callback( _self._elementStyler.getElementByName(
callback( _self._elementStyler.getElementByNameLax(
name, index, null, root
)[0] );
}

View File

@ -1,7 +1,7 @@
/**
* General UI logic for groups
*
* Copyright (C) 2015 LoVullo Associates, Inc.
* Copyright (C) 2015, 2016 LoVullo Associates, Inc.
*
* This file is part of liza.
*
@ -144,23 +144,45 @@ module.exports = Class( 'GroupUi' )
*/
'private _rawFieldCount': 0,
/**
* DOM group context
* @type {DomContext}
*/
'protected context': null,
/**
* Styler when fields are no longer applicable
* @type {FieldStyler}
*/
'private _naStyler': null,
/**
* Initializes GroupUi
*
* @param Group group group to style
* @param jQuery $content the group content
* @param ElementStyler styler styler to use to style elements
* @param jQuery jquery jQuery-compatible object
* @todo three of the below parameters might be able to be removed by
* using context instead; the separate context is transitional
* (refactoring).
*
* @param {Group} group group to style
* @param {jQuery} $content the group content
* @param {ElementStyler} styler styler to use to style elements
* @param {jQuery} jquery jQuery-compatible object
* @param {DomContext} context group context
* @param {FieldStyler} na_styler styler for fields that are N/A
*
* @return {undefined}
*/
'public __construct': function( group, $content, styler, jquery )
'public __construct': function(
group, $content, styler, jquery, context, na_styler
)
{
this.group = group;
this.$content = $content;
this.styler = styler;
this._jquery = jquery;
this.group = group;
this.$content = $content;
this.styler = styler;
this._jquery = jquery;
this.context = context;
this._naStyler = na_styler;
},
@ -787,14 +809,8 @@ module.exports = Class( 'GroupUi' )
'virtual protected doHideField': function( field, index )
{
var $elements = this.getFieldElements( field, index );
$elements.stop( true, true ).slideUp( 500, function()
{
// be sure to remove the display:none added by jQuery so that we can
// perform our own handling of what it means to be "hidden"
$elements.addClass( 'hidden' ).attr( 'style', '' );
} );
this.context.getFieldByName( field, index )
.applyStyle( this._naStyler );
},
@ -815,12 +831,8 @@ module.exports = Class( 'GroupUi' )
'virtual protected doShowField': function( field, index )
{
var $elements = this.getFieldElements( field, index );
$elements.find( '.hidden' ).andSelf()
.stop( true, true )
.removeClass( 'hidden' )
.slideDown( 500 );
this.context.getFieldByName( field, index )
.revokeStyle( this._naStyler );
},

View File

@ -1,7 +1,7 @@
/**
* Group tabbed UI
*
* Copyright (C) 2015 LoVullo Associates, Inc.
* Copyright (C) 2015, 2016 LoVullo Associates, Inc.
*
* This file is part of liza.
*
@ -373,23 +373,11 @@ module.exports = Class( 'TabbedGroupUi' )
{
_self.doHideField( field, index, true );
}, 25 );
return;
}
var $elements = this.getFieldElements( field, index );
$elements.stop( true, true );
if ( this.isOnVisibleTab( field, index ) )
{
$elements.slideUp( 500, function()
{
$( this ).addClass( 'hidden' );
} );
}
else
{
$elements.hide().addClass( 'hidden' );
}
this.__super( field, index );
},
@ -405,26 +393,11 @@ module.exports = Class( 'TabbedGroupUi' )
{
_self.doShowField( field, index, true );
}, 25 );
return;
}
var $elements = this.getFieldElements( field, index );
// it's important to stop animations *before* removing the hidden class,
// since forcing its completion may add it
$elements
.stop( true, true )
.find( '.hidden' )
.andSelf()
.removeClass( 'hidden' );
if ( this.isOnVisibleTab( field, index ) )
{
$elements.slideDown( 500 );
}
else
{
$elements.show();
}
this.__super( field, index );
},

View File

@ -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.
* - jQuery must be eliminated.

View File

@ -21,7 +21,6 @@
* @needsLove
* - Global references to jQuery must be removed.
* - Dependencies need to be liberated:
* - ElementStyler;
* - UI.
* - This may not be needed, may be able to be handled differently, and
* really should load from data rather than a pre-generated template (?)

View File

@ -0,0 +1,150 @@
/**
* Error condition field 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 <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
FieldStyler = require( './FieldStyler' );
/**
* Style field to indicate an error and displays an error message
*/
module.exports = Class( 'ErrorFieldStyler' )
.extend( FieldStyler,
{
'public getId': function()
{
return 'error';
},
/**
* Determines whether the field has been styled
*
* Having this predicate on the styler rather than the field ensures
* that, even if the two somehow get out of sync (or styles are applied
* elsewhere), application/revocation will function sanely.
*
* @param {DomField} field field to style
* @param {HTMLElement} element DOM element to style
*
* @return {boolean} whether FIELD has been styled by this styler
*/
'public isApplied': function( field, element )
{
return /\binvalid_field\b/.test( element.className );
},
'public applyStyle': function( field, element, row, msg )
{
var _self = this;
// style the row containing the element
for ( var i in row )
{
this.addClass( row[ i ], 'invalid' );
}
// TODO: legacy; remove
this.addClass( element, 'invalid_field' );
// display the error message
this._createMessage( field.getName(), msg, row[ 0 ], row[ 1 ] );
return this;
},
'public revokeStyle': function( field, element, row )
{
var _self = this;
// un-style the row containing the element
// style the row containing the element
for ( var i in row )
{
this.removeClass( row[ i ], 'invalid' );
}
// TODO: legacy; remove
this.removeClass( element, 'invalid_field' );
this._destroyMessage( row[ 0 ], row[ 1 ] );
return this;
},
'private _createMessage': function( name, message, dd, dt )
{
// we can only generate the message if the parent row is available
if ( !( dd && dt ) )
{
return;
}
var msg = document.createElement( 'div' );
msg.className = 'errmsg';
msg.innerHTML = message;
// append to dd
dd.appendChild( msg );
var height = ( msg.offsetTop + msg.offsetHeight );
// element does not have height until added to DOM
// set a default to ensure it appears to user
height = ( height === 0 )
? 45 + 'px'
: ( height + 10 ) + 'px';
dd.style.height = height;
dt.style.height = height;
},
'private _destroyMessage': function( dd, dt )
{
if ( !dd )
{
return;
}
dd.style.height = '';
// note that dt may not actually exist (in fact, dd may not even be a
// dd; we should rename these variables)
dt && ( dt.style.height = '' );
var node;
// search for the message node, starting with the last element (since
// the error message was appended, we're likely to find it on our first
// try)
for ( node = dd.lastChild;
node && node.className !== 'errmsg';
node = node.previousSibling
);
// if we found it, then remove it
node && dd.removeChild( node );
}
} );

View File

@ -0,0 +1,108 @@
/**
* Error condition field 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 <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
Styler = require( './Styler' );
/**
* Handle error generation and defer styling to supertype
*/
module.exports = Class( 'ErrorStyler' )
.implement( Styler )
.extend(
{
/**
* Hash of error messages by field name
* @type {Object}
*/
'private _msgs': {},
/**
* Initialize error styler with a hash of error messages by field name
*
* @param {Object} msgs hash of error messages by field name
*/
'virtual __construct': function( msgs )
{
this._msgs = msgs;
},
'public getHooks': function( uistyler )
{
var _self = this;
return {
fieldError: function( context, failures, msgs )
{
msgs = msgs || {};
for ( var name in failures )
{
var msgset = ( msgs[ name ] || [] );
for ( var index in failures[ name ] )
{
// if no error message was provided, fall back to one of
// the defaults
var msg = (
msgset[ index ]
|| _self._msgs[ name ]
|| "Field is invalid"
);
_self.onFieldError(
context.getFieldByName( name, index ),
msg
);
}
}
},
fieldFixed: function( context, fixed )
{
for ( var name in fixed )
{
for ( var index in fixed[ name ] )
{
_self.onFieldFixed(
context.getFieldByName( name, index )
);
}
}
}
};
},
'virtual protected onFieldError': function( field, msg )
{
// do nothing by default
},
'virtual protected onFieldFixed': function( field )
{
// do nothing by default
}
} );

View File

@ -0,0 +1,152 @@
/**
* Style fields using CSS
*
* 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 <http://www.gnu.org/licenses/>.
*/
var AbstractClass = require( 'easejs' ).AbstractClass;
/**
* Style DOM fields
*
* @todo perhaps this should be called DomFieldStyler
*/
module.exports = AbstractClass( 'FieldStyler',
{
/**
* Retrieve unique identifier
*
* @return {string} unique identifier
*/
'abstract public getId': [],
/**
* Determines whether the field has been styled
*
* Having this predicate on the styler rather than the field ensures
* that, even if the two somehow get out of sync (or styles are applied
* elsewhere), application/revocation will function sanely.
*
* @param {DomField} field field to style
* @param {HTMLElement} element DOM element to style
*
* @return {boolean} whether FIELD has been styled by this styler
*/
'abstract public isApplied': [ 'field', 'element' ],
/**
* Apply style to field
*
* @param {DomField} field field to style
* @param {HTMLElement} element DOM element to style
* @param {Array.<HTMLElement>} row DOM elements of containing row
*
* @return {FieldStyler} self
*/
'abstract public applyStyle': [ 'field', 'element', 'row' ],
/**
* Remove style from field
*
* @param {DomField} field field to unstyle
* @param {HTMLElement} element DOM element to unstyle
* @param {Array.<HTMLElement>} row DOM elements of containing row
*
* @return {FieldStyler} self
*/
'abstract public revokeStyle': [ 'field', 'element', 'row' ],
/**
* Add CSS class CLS to element ELEMENT
*
* This method is needed until support is dropped for browsers that do
* not support classList.
*
* @param {HTMLElement} element DOM element to style
* @param {string} cls class name
*
* @return {FieldStyler} self
*/
'protected addClass': function( element, cls )
{
if ( !element )
{
return this;
}
// if we are given an array, then recurse
if ( Array.isArray( element ) )
{
for ( var i in element )
{
this.addClass( element[ i ], cls );
}
return;
}
else if ( typeof element.className === 'string' )
{
element.className += ' ' + cls;
}
return this;
},
/**
* Add CSS class CLS to element ELEMENT
*
* This method is needed until support is dropped for browsers that do
* not support classList.
*
* @param {HTMLElement} element DOM element to style
* @param {string} cls class name
*
* @return {FieldStyler} self
*/
'protected removeClass': function( element, cls )
{
if ( !element )
{
return this;
}
// if we are given an array, then recurse
if ( Array.isArray( element ) )
{
for ( var i in element )
{
this.removeClass( element[ i ], cls );
}
return;
}
else if ( typeof element.className === 'string' )
{
// note that we use a space instead of a boundary for the character
// preceding the match due to the implementation of addClass()
element.className = element.className.replace(
new RegExp( ( ' ' + cls + '\\b' ), 'g' ), ''
);
}
return this;
}
} );

View File

@ -0,0 +1,158 @@
/**
* N/A field 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 <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
FieldStyler = require( './FieldStyler' );
/**
* Style fields that are not applicable (and so do not need to collect data
* from the user)
*
* @todo Detaching should be done by DomField
*/
module.exports = Class( 'NaFieldStyler' )
.extend( FieldStyler,
{
/**
* Retrieve unique identifier
*
* @return {string} unique identifier
*/
'public getId': function()
{
return 'na';
},
/**
* Determines whether the field has been styled
*
* Having this predicate on the styler rather than the field ensures
* that, even if the two somehow get out of sync (or styles are applied
* elsewhere), application/revocation will function sanely.
*
* @param {DomField} field field to style
* @param {HTMLElement} element DOM element to style
*
* @return {boolean} whether FIELD has been styled by this styler
*/
'public isApplied': function( field, element )
{
return /\bhidden\b/.test( element.className );
},
/**
* Apply style to field
*
* @param {DomField} field field to style
* @param {HTMLElement} element DOM element to style
* @param {Array.<HTMLElement>} row DOM elements of containing row
*
* @return {FieldStyler} self
*/
'public applyStyle': function( field, element, row )
{
if ( this.isSubField( field ) )
{
this.hideField( element, [] );
field.getParent().removeChild( element );
// this is a child of another field; don't consider it a
// containing row, since we don't want our operations affecting
// it
return;
}
this.hideField( element, row );
},
/**
* Remove style from field
*
* @param {DomField} field field to unstyle
* @param {HTMLElement} element DOM element to unstyle
* @param {Array.<HTMLElement>} row DOM elements of containing row
*
* @return {FieldStyler} self
*/
'public revokeStyle': function( field, element, row )
{
if ( this.isSubField( field ) )
{
this.showField( element, [] );
field.getParent().appendChild( element );
return;
}
this.showField( element, row );
},
/**
* Determine whether element ELEMENT represents a sub-field
*
* A sub-field is a field within a field; the distinction is important
* because we probably don't want operations on a sub-field affecting
* its parent.
*
* @todo: move somewhere else (Field perhaps?)
*
* @param {HTMLElement} element DOM element associated with field
*
* @return {boolean} whether ELEMENT represents a sub-field
*/
'protected isSubField': function( field )
{
var parent = field.getParent();
// ES3-compatible (don't use classList)
return !!( parent && /\bwidget\b/.test( parent.className ) );
},
'virtual protected hideField': function( element, row )
{
this.addClass( element, 'hidden' );
// this is a workaround from the old days where jQuery would add
// styles to hide elements, which we wanted to override; this can be
// removed once jQuery is eradicated from the framework
element.style = '';
for ( var i in row )
{
this.addClass( row[ i ], 'hidden' );
row[ i ].style = '';
}
},
'virtual protected showField': function( element, row )
{
this.removeClass( element, 'hidden' );
this.removeClass( row, 'hidden' );
}
} );

View File

@ -0,0 +1,97 @@
/**
* Animated N/A field 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 <http://www.gnu.org/licenses/>.
*/
var Trait = require( 'easejs' ).Trait,
NaFieldStyler = require( './NaFieldStyler' );
/**
* Sliding animations for field show/hide
*
* @todo Use CSS3 once we can drop support for IE<10
*/
module.exports = Trait.extend( NaFieldStyler,
{
/**
* jQuery instance
* @type {jQuery}
*/
'private _jquery': null,
/**
* Prepare mixin with jQuery instance
*
* @param {jQuery} jquery jQuery instance
*/
__mixin: function( jquery )
{
this._jquery = jquery;
},
/**
* Animate field display
*
* When a field becomes applicable, progressively increase its height
* ("slide down").
*
* @param {HTMLElement} element field DOM element
* @param {Array.<HTMLElement} row parent row elements
*
* @return {undefined}
*/
'override protected showField': function( element, row )
{
var $row = this._jquery( row );
$row.stop( true, true );
this.__super( element, row );
$row
.hide()
.slideDown( 500 );
},
/**
* Animate field hiding
*
* When a field becomes non-applicable, progressively decrease its
* height ("slide up").
*
* @param {HTMLElement} element field DOM element
* @param {Array.<HTMLElement} row parent row elements
*
* @return {undefined}
*/
'override protected hideField': function( element, row )
{
var _self = this,
all = [ element ].concat( row ),
$elements = this._jquery( all );
$elements.stop( true, true )
.slideUp( 500, function()
{
_self.hideField['super'].call( _self, element, row );
} );
}
} );

View File

@ -0,0 +1,66 @@
/**
* Style errors in sidebar
*
* 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 <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
ErrorStyler = require( './ErrorStyler' );
/**
* Displays errors in the sidebar
*
* TODO: This is an adapter around the old system; it could use some
* refactoring.
*/
module.exports = Class( 'SidebarErrorStyler' )
.extend( ErrorStyler,
{
/**
* Error box in which to display errors
* @type {FormErrorBox}
*/
'private _errbox': null,
/**
* Ui instance
* @type {Ui}
*/
'private _ui': null,
'override __construct': function( msgs, error_box, ui )
{
this._errbox = error_box;
this._ui = ui;
this.__super( msgs );
},
'override protected onFieldError': function( field, msg )
{
this._errbox.show( field.getName(), field.getIndex(), msg );
},
'override protected onFieldFixed': function( field )
{
this._errbox.removeError( field.getName(), field.getIndex() );
}
} );

View File

@ -0,0 +1,52 @@
/**
* Styles errors on steps
*
* 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 <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class,
ErrorStyler = require( './ErrorStyler' );
/**
* Trigger field styling for errors on the parent step itself
*/
module.exports = Class( 'StepErrorStyler' )
.extend( ErrorStyler,
{
'private _style': null,
'override __construct': function( msgs, field_style )
{
this._style = field_style;
this.__super( msgs );
},
'override protected onFieldError': function( field, msg )
{
field.applyStyle( this._style, msg );
},
'override protected onFieldFixed': function( field )
{
field.revokeStyle( this._style );
}
} );

View File

@ -0,0 +1,30 @@
/**
* Styler interface
*
* 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 <http://www.gnu.org/licenses/>.
*/
var Interface = require( 'easejs' ).Interface;
/**
* @todo Can be used for type hinting, but we need an actual API!
*/
module.exports = Interface( 'Styler',
{
} );

View File

@ -0,0 +1,322 @@
/**
* Test case for NaFieldStyler
*
* Copyright (C) 2016 LoVullo Associates, Inc.
*
* This file is part of the Liza Data Collection Framework
*
* 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 <http://www.gnu.org/licenses/>.
*/
var styler = require( '../../../' ).ui.styler,
expect = require( 'chai' ).expect,
Class = require( 'easejs' ).Class,
Sut = styler.NaFieldStyler;
describe( 'ui.styler.NaFieldStyler', function()
{
function testApplyHidden()
{
var element = { className: '' },
r1 = { className: '' },
r2 = { className: '' },
row = [ r1, r2 ];
Sut().applyStyle( getStubField( element ), element, row );
[ element, r1, r2 ].forEach( function( ele )
{
expect( ele.className ).to.match( /\bhidden\b/ );
} );
}
function testApplyClear()
{
var element = { style: 'foo' },
r1 = { style: 'foo' },
r2 = { style: 'foo' },
row = [ r1, r2 ];
Sut().applyStyle( getStubField( element ), element, row );
[ element, r1, r2 ].forEach( function( ele )
{
expect( ele.style ).to.equal( '' );
} );
}
function testRevokeHidden()
{
var element = { className: 'foo hidden' },
r1 = { className: 'foo hidden' },
r2 = { className: 'foo hidden' },
row = [ r1, r2 ];
Sut().revokeStyle( getStubField( element ), element, row );
[ element, r1, r2 ].forEach( function( ele )
{
expect( ele.className ).to.not.match( /\bhidden\b/ );
expect( ele.className ).to.match( /foo/ );
} );
}
function testRevokeStyle()
{
var element = { style: 'foo' },
r1 = { style: 'foo' },
r2 = { style: 'foo' },
row = [ r1, r2 ];
Sut().revokeStyle( getStubField( element ), element, row );
[ element, r1, r2 ].forEach( function( ele )
{
expect( ele.style ).to.equal( 'foo' );
} );
}
describe( '#getId', function()
{
it( 'returns unique identifier', function()
{
expect( Sut().getId() ).to.equal( 'na' );
} );
} );
describe( '#applyStyle', function()
{
it( 'sets hidden class on all elements', testApplyHidden );
it( 'clears style on all elements', testApplyClear );
it( 'does not set class on subfield parents', function()
{
var element = {
className: '',
parentElement: {
className: 'widget',
removeChild: function() {},
}
};
var r1 = { className: '' },
r2 = { className: '' },
row = [ r1, r2 ];
Sut().applyStyle( getStubField( element ), element, row );
expect( element.className ).to.match( /\bhidden\b/ );
[ r1, r2 ].forEach( function( ele )
{
expect( ele.className ).to.equal( '' );
} );
} );
it( 'does not clears style subfield parents', function()
{
var element = {
style: 'foo',
parentElement: {
className: 'widget',
removeChild: function() {},
}
};
var r1 = { style: 'foo' },
r2 = { style: 'foo' },
row = [ r1, r2 ];
Sut().applyStyle( getStubField( element ), element, row );
expect( element.style ).to.equal( '' );
[ r1, r2 ].forEach( function( ele )
{
expect( ele.style ).to.equal( 'foo' );
} );
} );
// f@#(& IE
it( 'removes subfield from DOM', function( done )
{
var element = {
style: '',
parentElement: {
className: 'widget',
removeChild: function( ele )
{
expect( ele ).to.equal( element );
done();
},
}
};
Sut().applyStyle( getStubField( element ), element, [] );
} );
} );
describe( '#revokeStyle', function()
{
it( 'removes hidden class on all elements', testRevokeHidden );
it( 'does not clear style on all elements', testRevokeStyle );
it( 'does not remove hidden class on subfield parents', function()
{
var element = {
className: 'foo hidden',
parentElement: {
className: 'widget',
appendChild: function() {},
}
};
var r1 = { className: 'foo hidden' },
r2 = { className: 'foo hidden' },
row = [ r1, r2 ];
Sut().revokeStyle( getStubField( element ), element, row );
expect( element.className ).to.not.match( /\bhidden\b/ );
expect( element.className ).to.match( /foo/ );
[ r1, r2 ].forEach( function( ele )
{
expect( ele.className ).to.equal( 'foo hidden' );
} );
} );
// we eventually need to care about where it's re-attached
it( 're-attaches subfield to DOM', function( done )
{
var element = {
className: '',
parentElement: {
className: 'widget',
appendChild: function( ele )
{
expect( ele ).to.equal( element );
done();
},
}
};
Sut().revokeStyle( getStubField( element ), element, [] );
} );
} );
describe( '#isApplied', function()
{
it( 'recognizes when applied', function()
{
var element = {
className: '',
};
var sut = Sut(),
field = getStubField( element );
sut.applyStyle( field, element, [] );
expect( sut.isApplied( field, element ) )
.to.be.true;
sut.revokeStyle( field, element, [] );
expect( sut.isApplied( field, element ) )
.to.be.false;
} );
} );
describe( 'protected API', function()
{
describe( '#isSubField', function()
{
it( 'recognizes parent widget class as subfield', function()
{
var element = {
className: '',
parentElement: {
className: 'widget',
removeChild: function() {},
}
};
expect( protSut().protIsSubField( getStubField( element ) ) )
.to.be.true;
} );
it( 'missing parent widget class is non-subfield', function()
{
var element = {
className: '',
};
expect( protSut().protIsSubField( getStubField( element ) ) )
.to.be.false;
} );
} );
describe( '#hideField', function()
{
it( 'sets hidden class on all elements', testApplyHidden );
it( 'clears style on all elements', testApplyClear );
} );
describe( '#showField', function()
{
it( 'removes hidden class on all elements', testRevokeHidden );
it( 'does not clear style on all elements', testRevokeStyle );
} );
} );
} );
function getStubField( element )
{
return {
getParent: function()
{
return element.parentElement;
}
};
}
function protSut()
{
return Class.extend( Sut, {
protIsSubField: function( element )
{
return this.isSubField( element );
}
} )();
}