1
0
Fork 0

Liberate {,ui/}field/

master
Mike Gerwitz 2015-11-30 14:22:34 -05:00
parent 4cc240e977
commit 9a1dd337eb
4 changed files with 486 additions and 0 deletions

View File

@ -0,0 +1,60 @@
/**
* Field representing bucket value
*
* Copyright (C) 2015 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,
Field = require( './Field' );
module.exports = Class( 'BucketField' )
.implement( Field )
.extend(
{
/**
* Field name
* @type {string}
*/
'private _name': '',
/**
* Field index
* @type {string}'
*/
'private _index': 0,
__construct: function( name, index )
{
this._name = ''+name;
this._index = +index;
},
'public getName': function()
{
return this._name;
},
'public getIndex': function()
{
return this._index;
}
} );

31
src/field/Field.js 100644
View File

@ -0,0 +1,31 @@
/**
* Field representation
*
* Copyright (C) 2015 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;
module.exports = Interface( 'Field',
{
'public getName': [],
'public getIndex': []
} );

View File

@ -0,0 +1,300 @@
/**
* Field represented by DOM element
*
* Copyright (C) 2015 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,
Field = require( '../../field/Field' ),
EventEmitter = require( 'events' ).EventEmitter;
module.exports = Class( 'DomField' )
.implement( Field )
.extend( EventEmitter,
{
/**
* Wrapped field
* @type {Field}
*/
'private _field': null,
'private _element': null,
'private _idPrefix': 'q_',
/**
* Currently active styles
* @type {Object}
*/
'private _styles': {},
__construct: function( field, element )
{
if ( !( Class.isA( Field, field ) ) )
{
throw TypeError( "Invalid field provided" );
}
this._field = field;
this._element = element;
},
'public proxy getName': '_field',
'public proxy getIndex': '_field',
'private _getElement': function( callback )
{
// if the provided root is a function, then it should be lazily laoded
if ( this._element === 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' )
{
var _self = this,
f = this._element;
// 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 )
{
queue.push( c );
};
// attempt to retrieve our element from the DOM
f( function( element )
{
if ( !element )
{
_self._element = null;
_self.emit( 'error', Error(
"Cannot locate DOM element for field " +
_self.getName() + "[" + _self.getIndex() + "]"
) );
// do not even finish; this shit is for real.
return;
}
_self._element = element;
callback( element );
// if we have any queued requests, process them when we're not
// busy
var c;
while ( c = queue.shift() )
{
setTimeout( function()
{
// return the element to the queued callback
c( element );
}, 25 );
}
} );
return;
}
// we already have the element; immediately return it
callback( this._element );
},
'private _hasStyle': function( style )
{
return !!this._styles[ style.getId() ];
},
'private _flagStyle': function( style, flag )
{
this._styles[ style.getId() ] = !!flag;
},
'public applyStyle': function( style )
{
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 )
{
style.applyStyle.apply(
style,
[ _self.__inst, root, _self.getContainingRow() ].concat( sargs )
);
} );
return this;
},
'public revokeStyle': function( style )
{
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 )
{
style.revokeStyle( _self.__inst, root, _self.getContainingRow() );
} );
return this;
},
/**
* Resolves a field into an id that may be used to query the DOM
*
* @return {string} expected id of element on the DOM
*/
'protected resolveId': function()
{
return this.doResolveId(
this._field.getName(),
this._field.getIndex()
);
},
/**
* Resolves a field into an id that may be used to query the DOM
*
* This may be overridden by a subtype to alter the resolution logic. The
* name and index are passed to the method to ensure that the field itself
* remains encapsulated.
*
* @param {string} name field name
* @param {number} index field index
*
* @return {string} expected id of element on the DOM
*/
'virtual protected doResolveId': function( name, index )
{
return ( this._idPrefix + name + '_' + index );
},
// TODO: move me
'protected getContainingRow': function()
{
var dd = this.getParent( this._element, 'dd' ),
dt = ( dd ) ? this.getPrecedingSibling( dd, 'dt' ) : null;
return ( dt )
? [ dd, dt ]
: [ this.getParent( this._element ) ];
},
'protected getParent': function( element, type )
{
var parent = element.parentElement;
if ( parent === null )
{
return null;
}
else if ( !type )
{
return parent;
}
// nodeName is in caps
if ( type.toUpperCase() === parent.nodeName )
{
return parent;
}
// otherwise, keep looking
return this.getParent( parent, type );
},
'protected getPrecedingSibling': function( element, type )
{
return this.getSibling( element, type, -1 );
},
'protected getFollowingSibling': function( element, type )
{
return this.getSibling( element, type, 1 );
},
'protected getSibling': function( element, type, direction )
{
// if no direction was provided, then search in both
if ( !direction )
{
return ( this.getSibling( element, type, -1 )
|| this.getSibling( element, type, 1 )
);
}
// get the next node relative to the direction
var next = element[
( direction === -1 ) ? 'previousSibling' : 'nextSibling'
];
if ( next === null )
{
return null;
}
// if we found our sibling, return it
if ( type.toUpperCase() === next.nodeName )
{
return next;
}
return this.getSibling( next, type, direction );
}
} );

View File

@ -0,0 +1,95 @@
/**
* Creates DomField
*
* Copyright (C) 2015 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/>.
*
* @needsLove
* - Dependencies need to be liberated:
* - ElementStyler.
* @end needsLove
*/
var Class = require( 'easejs' ).Class,
BucketField = require( '../../field/BucketField' ),
DomField = require( './DomField' );
module.exports = Class( 'DomFieldFactory',
{
'private _elementStyler': null,
__construct: function( element_styler )
{
this._elementStyler = element_styler;
},
/**
* Create a DomField from the given field description
*
* The provided DomField will wait to access the DOM until an operation
* requires it.
*
* @param {string} name field name
* @param {number} index field index
*
* @param {function(HtmlElement)|HtmlElement} root root element containing
* the field (optionally
* lazy)
*
* @return {DomField} generated field
*/
'public create': function( name, index, root )
{
var _self = this;
return DomField(
BucketField( name, index ),
// lazy load on first access
function( callback )
{
// are we lazy?
if ( typeof root === 'function' )
{
// wait to fulfill this request until after the element
// becomes available
root( function( result )
{
root = result;
c();
} );
return;
}
// not lazy; continue immediately
c();
function c()
{
callback( _self._elementStyler.getElementByName(
name, index, null, root
)[0] );
}
}
);
}
} );