Added es.DocumentLeafNode, which like es.DocumentBranchNode is a mixin-like class (we may want to switch to using a more natural composition mechanism than es.extendClass in the future) - now es.DocumentNode also has an abstract method called hasChildren which returns a boolean and can indicate if a node is a leaf or a branch.

This commit is contained in:
Trevor Parscal 2011-11-10 19:26:02 +00:00
parent a4f71ace69
commit 713a80596d
9 changed files with 114 additions and 58 deletions

View file

@ -69,8 +69,9 @@
<!-- Bases -->
<script src="../modules/es/bases/es.EventEmitter.js"></script>
<script src="../modules/es/bases/es.DocumentNode.js"></script>
<script src="../modules/es/bases/es.DocumentBranchNode.js"></script>
<script src="../modules/es/bases/es.DocumentModelNode.js"></script>
<script src="../modules/es/bases/es.DocumentBranchNode.js"></script>
<script src="../modules/es/bases/es.DocumentLeafNode.js"></script>
<script src="../modules/es/bases/es.DocumentModelBranchNode.js"></script>
<script src="../modules/es/bases/es.DocumentModelLeafNode.js"></script>
<script src="../modules/es/bases/es.DocumentViewNode.js"></script>

View file

@ -12,6 +12,17 @@ es.DocumentBranchNode = function( nodes ) {
/* Methods */
/**
* Checks if this node has child nodes.
*
* @method
* @see {es.DocumentNode.prototype.hasChildren}
* @returns {Boolean} Whether this node has children
*/
es.DocumentBranchNode.prototype.hasChildren = function() {
return true;
};
/**
* Gets a list of child nodes.
*
@ -33,21 +44,21 @@ es.DocumentBranchNode.prototype.getChildren = function() {
*/
es.DocumentBranchNode.prototype.getRangeFromNode = function( node, shallow ) {
if ( this.children.length ) {
var isBranch;
var childNode;
for ( var i = 0, length = this.children.length, left = 0; i < length; i++ ) {
if ( this.children[i] === node ) {
return new es.Range( left, left + this.children[i].getElementLength() );
childNode = this.children[i];
if ( childNode === node ) {
return new es.Range( left, left + childNode.getElementLength() );
}
isBranch = typeof this.children[i].getChildren === 'function';
if ( !shallow && isBranch && this.children[i].getChildren().length ) {
var range = this.children[i].getRangeFromNode( node );
if ( !shallow && childNode.hasChildren() && childNode.getChildren().length ) {
var range = childNode.getRangeFromNode( node );
if ( range !== null ) {
// Include opening of parent
left++;
return es.Range.newFromTranslatedRange( range, left );
}
}
left += this.children[i].getElementLength();
left += childNode.getElementLength();
}
}
return null;
@ -69,19 +80,19 @@ es.DocumentBranchNode.prototype.getRangeFromNode = function( node, shallow ) {
es.DocumentBranchNode.prototype.getOffsetFromNode = function( node, shallow ) {
if ( this.children.length ) {
var offset = 0,
isBranch;
childNode;
for ( var i = 0, length = this.children.length; i < length; i++ ) {
if ( this.children[i] === node ) {
childNode = this.children[i];
if ( childNode === node ) {
return offset;
}
isBranch = typeof this.children[i].getChildren === 'function';
if ( !shallow && isBranch && this.children[i].getChildren().length ) {
var childOffset = this.getOffsetFromNode.call( this.children[i], node );
if ( !shallow && childNode.hasChildren() && childNode.getChildren().length ) {
var childOffset = this.getOffsetFromNode.call( childNode, node );
if ( childOffset !== -1 ) {
return offset + 1 + childOffset;
}
}
offset += this.children[i].getElementLength();
offset += childNode.getElementLength();
}
}
return -1;
@ -105,20 +116,20 @@ es.DocumentBranchNode.prototype.getNodeFromOffset = function( offset, shallow )
if ( this.children.length ) {
var nodeOffset = 0,
nodeLength,
isBranch;
childNode;
for ( var i = 0, length = this.children.length; i < length; i++ ) {
childNode = this.children[i];
if ( offset == nodeOffset ) {
// The requested offset is right before this.children[i],
// The requested offset is right before childNode,
// so it's not inside any of this's children, but inside this
return this;
}
nodeLength = this.children[i].getElementLength();
nodeLength = childNode.getElementLength();
if ( offset >= nodeOffset && offset < nodeOffset + nodeLength ) {
isBranch = typeof this.children[i].getChildren === 'function';
if ( !shallow && isBranch && this.children[i].getChildren().length ) {
return this.getNodeFromOffset.call( this.children[i], offset - nodeOffset - 1 );
if ( !shallow && childNode.hasChildren() && childNode.getChildren().length ) {
return this.getNodeFromOffset.call( childNode, offset - nodeOffset - 1 );
} else {
return this.children[i];
return childNode;
}
}
nodeOffset += nodeLength;
@ -155,7 +166,8 @@ es.DocumentBranchNode.prototype.selectNodes = function( range, shallow ) {
start = range.start,
end = range.end,
startInside,
endInside;
endInside,
childNode;
if ( start < 0 ) {
throw 'The start offset of the range is negative';
@ -173,20 +185,21 @@ es.DocumentBranchNode.prototype.selectNodes = function( range, shallow ) {
// This node has children, loop over them
left = 1; // First offset inside the first child. Offset 0 is before the first child
for ( i = 0; i < this.children.length; i++ ) {
// left <= any offset inside this.children[i] <= right
right = left + this.children[i].getContentLength();
childNode = this.children[i];
// left <= any offset inside childNode <= right
right = left + childNode.getContentLength();
if ( start == end && ( start == left - 1 || start == right + 1 ) ) {
// Empty range outside of any node
return [];
}
startInside = start >= left && start <= right; // is the start inside this.children[i]?
endInside = end >= left && end <= right; // is the end inside this.children[i]?
startInside = start >= left && start <= right; // is the start inside childNode?
endInside = end >= left && end <= right; // is the end inside childNode?
if ( startInside && endInside ) {
// The range is entirely inside this.children[i]
if ( shallow || !this.children[i].children ) {
// The range is entirely inside childNode
if ( shallow || !childNode.children ) {
// For leaf nodes, use the same behavior as for shallow calls.
// A proper recursive function would let the recursion handle this,
// but the leaves don't have .selectNodes() because they're not DocumentBranchNodes
@ -194,14 +207,14 @@ es.DocumentBranchNode.prototype.selectNodes = function( range, shallow ) {
// TODO should probably rewrite this recursive function as an iterative function anyway, probably faster
nodes = [
{
'node': this.children[i],
'node': childNode,
'range': new es.Range( start - left, end - left ),
'globalRange': new es.Range( start, end )
}
];
} else {
// Recurse into this.children[i]
nodes = this.children[i].selectNodes( new es.Range( start - left, end - left ) );
// Recurse into childNode
nodes = childNode.selectNodes( new es.Range( start - left, end - left ) );
// Adjust globalRange
for ( j = 0; j < nodes.length; j++ ) {
if ( nodes[j].globalRange !== undefined ) {
@ -209,48 +222,48 @@ es.DocumentBranchNode.prototype.selectNodes = function( range, shallow ) {
}
}
}
// Since the start and end are both inside this.children[i], we know for sure that we're
// Since the start and end are both inside childNode, we know for sure that we're
// done, so return
return nodes;
} else if ( startInside ) {
// The start is inside this.children[i] but the end isn't
// Add a range from the start of the range to the end of this.children[i]
// The start is inside childNode but the end isn't
// Add a range from the start of the range to the end of childNode
nodes.push( {
'node': this.children[i],
'node': childNode,
'range': new es.Range( start - left, right - left ),
'globalRange': new es.Range( start, right )
} );
} else if ( endInside ) {
// The end is inside this.children[i] but the start isn't
// Add a range from the start of this.children[i] to the end of the range
// The end is inside childNode but the start isn't
// Add a range from the start of childNode to the end of the range
nodes.push( {
'node': this.children[i],
'node': childNode,
'range': new es.Range( 0, end - left ),
'globalRange': new es.Range( left, end )
} );
// We've found the end, so we're done
return nodes;
} else if ( end == right + 1 ) {
// end is between this.children[i] and this.children[i+1]
// start is not inside this.children[i], so the selection covers
// all of this.children[i], then ends
nodes.push( { 'node': this.children[i] } );
// end is between childNode and this.children[i+1]
// start is not inside childNode, so the selection covers
// all of childNode, then ends
nodes.push( { 'node': childNode } );
// We've reached the end so we're done
return nodes;
} else if ( start == left - 1 ) {
// start is between this.children[i-1] and this.children[i]
// end is not inside this.children[i], so the selection covers
// all of this.children[i] and more
nodes.push( { 'node': this.children[i] } );
// start is between this.children[i-1] and childNode
// end is not inside childNode, so the selection covers
// all of childNode and more
nodes.push( { 'node': childNode } );
} else if ( nodes.length > 0 ) {
// Neither the start nor the end is inside this.children[i], but nodes is non-empty,
// so this.children[i] must be between the start and the end
// Neither the start nor the end is inside childNode, but nodes is non-empty,
// so childNode must be between the start and the end
// Add the entire node, so no range property
nodes.push( { 'node': this.children[i] } );
nodes.push( { 'node': childNode } );
}
// Move left to the start of this.children[i+1] for the next iteration
// We use +2 because we need to jump over the offset between this.children[i] and
// We use +2 because we need to jump over the offset between childNode and
// this.children[i+1]
left = right + 2;
if ( end < left ) {

View file

@ -0,0 +1,23 @@
/**
* Creates an es.DocumentLeafNode object.
*
* @class
* @abstract
* @constructor
*/
es.DocumentLeafNode = function() {
//
};
/* Methods */
/**
* Checks if this node has child nodes.
*
* @method
* @see {es.DocumentNode.prototype.hasChildren}
* @returns {Boolean} Whether this node has children
*/
es.DocumentLeafNode.prototype.hasChildren = function() {
return false;
};

View file

@ -4,16 +4,16 @@
* @class
* @abstract
* @constructor
* @extends {es.DocumentModelNode}
* @extends {es.DocumentBranchNode}
* @extends {es.DocumentModelNode}
* @param {String} type Symbolic name of node type
* @param {Object} element Element object in document data
* @param {es.DocumentModelBranchNode[]} [contents] List of child nodes to append
*/
es.DocumentModelBranchNode = function( type, element, contents ) {
// Inheritance
es.DocumentModelNode.call( this, type, element, 0 );
es.DocumentBranchNode.call( this );
es.DocumentModelNode.call( this, type, element, 0 );
// Child nodes
if ( es.isArray( contents ) ) {
@ -240,5 +240,5 @@ es.DocumentModelBranchNode.prototype.clearRoot = function() {
/* Inheritance */
es.extendClass( es.DocumentModelBranchNode, es.DocumentModelNode );
es.extendClass( es.DocumentModelBranchNode, es.DocumentBranchNode );
es.extendClass( es.DocumentModelBranchNode, es.DocumentModelNode );

View file

@ -4,14 +4,15 @@
* @class
* @abstract
* @constructor
* @extends {es.DocumentLeafNode}
* @extends {es.DocumentModelNode}
* @extends {es.DocumentNode}
* @param {String} type Symbolic name of node type
* @param {Object} element Element object in document data
* @param {Integer} [length] Length of content data in document
*/
es.DocumentModelLeafNode = function( type, element, length ) {
// Inheritance
es.DocumentLeafNode.call( this );
es.DocumentModelNode.call( this, type, element, length );
// Properties
@ -80,4 +81,5 @@ es.DocumentModelLeafNode.prototype.getText = function( range ) {
/* Inheritance */
es.extendClass( es.DocumentModelLeafNode, es.DocumentLeafNode );
es.extendClass( es.DocumentModelLeafNode, es.DocumentModelNode );

View file

@ -23,6 +23,7 @@ es.DocumentNode = function() {
* Gets the content length.
*
* @method
* @abstract
* @returns {Integer} Length of content
*/
es.DocumentNode.prototype.getContentLength = function() {
@ -33,12 +34,24 @@ es.DocumentNode.prototype.getContentLength = function() {
* Gets the element length.
*
* @method
* @abstract
* @returns {Integer} Length of content
*/
es.DocumentNode.prototype.getElementLength = function() {
throw 'DocumentNode.getElementLength not implemented in this subclass:' + this.constructor;
};
/**
* Checks if this node has child nodes.
*
* @method
* @abstract
* @returns {Boolean} Whether this node has children
*/
es.DocumentNode.prototype.hasChildren = function() {
throw 'DocumentNode.hasChildren not implemented in this subclass:' + this.constructor;
};
/* Inheritance */
es.extendClass( es.DocumentNode, es.EventEmitter );

View file

@ -4,15 +4,15 @@
* @class
* @abstract
* @constructor
* @extends {es.DocumentViewNode}
* @extends {es.DocumentBranchNode}
* @extends {es.DocumentViewNode}
* @param model {es.ModelNode} Model to observe
* @param {jQuery} [$element] Element to use as a container
*/
es.DocumentViewBranchNode = function( model, $element, horizontal ) {
// Inheritance
es.DocumentViewNode.call( this, model, $element );
es.DocumentBranchNode.call( this );
es.DocumentViewNode.call( this, model, $element );
// Properties
this.horizontal = horizontal || false;
@ -243,5 +243,5 @@ es.DocumentViewBranchNode.prototype.getRenderedLineRangeFromOffset = function( o
/* Inheritance */
es.extendClass( es.DocumentViewBranchNode, es.DocumentViewNode );
es.extendClass( es.DocumentViewBranchNode, es.DocumentBranchNode );
es.extendClass( es.DocumentViewBranchNode, es.DocumentViewNode );

View file

@ -4,12 +4,14 @@
* @class
* @abstract
* @constructor
* @extends {es.DocumentLeafNode}
* @extends {es.DocumentViewNode}
* @param model {es.ModelNode} Model to observe
* @param {jQuery} [$element] Element to use as a container
*/
es.DocumentViewLeafNode = function( model, $element ) {
// Inheritance
es.DocumentLeafNode.call( this );
es.DocumentViewNode.call( this, model, $element );
// Properties
@ -80,4 +82,5 @@ es.DocumentViewLeafNode.prototype.getRenderedLineRangeFromOffset = function( off
/* Inheritance */
es.extendClass( es.DocumentViewLeafNode, es.DocumentLeafNode );
es.extendClass( es.DocumentViewLeafNode, es.DocumentViewNode );

View file

@ -23,8 +23,9 @@
<!-- Bases -->
<script src="../../modules/es/bases/es.EventEmitter.js"></script>
<script src="../../modules/es/bases/es.DocumentNode.js"></script>
<script src="../../modules/es/bases/es.DocumentBranchNode.js"></script>
<script src="../../modules/es/bases/es.DocumentModelNode.js"></script>
<script src="../../modules/es/bases/es.DocumentBranchNode.js"></script>
<script src="../../modules/es/bases/es.DocumentLeafNode.js"></script>
<script src="../../modules/es/bases/es.DocumentModelBranchNode.js"></script>
<script src="../../modules/es/bases/es.DocumentModelLeafNode.js"></script>