From 713a80596d61863ef7afa2368f239749047254e1 Mon Sep 17 00:00:00 2001 From: Trevor Parscal Date: Thu, 10 Nov 2011 19:26:02 +0000 Subject: [PATCH] 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. --- demo/index.html | 3 +- modules/es/bases/es.DocumentBranchNode.js | 111 ++++++++++-------- modules/es/bases/es.DocumentLeafNode.js | 23 ++++ .../es/bases/es.DocumentModelBranchNode.js | 6 +- modules/es/bases/es.DocumentModelLeafNode.js | 4 +- modules/es/bases/es.DocumentNode.js | 13 ++ modules/es/bases/es.DocumentViewBranchNode.js | 6 +- modules/es/bases/es.DocumentViewLeafNode.js | 3 + tests/es/index.html | 3 +- 9 files changed, 114 insertions(+), 58 deletions(-) create mode 100644 modules/es/bases/es.DocumentLeafNode.js diff --git a/demo/index.html b/demo/index.html index f814a5fd0c..489ea1dab7 100644 --- a/demo/index.html +++ b/demo/index.html @@ -69,8 +69,9 @@ - + + diff --git a/modules/es/bases/es.DocumentBranchNode.js b/modules/es/bases/es.DocumentBranchNode.js index daac352bc3..e26c758f74 100644 --- a/modules/es/bases/es.DocumentBranchNode.js +++ b/modules/es/bases/es.DocumentBranchNode.js @@ -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 ) { diff --git a/modules/es/bases/es.DocumentLeafNode.js b/modules/es/bases/es.DocumentLeafNode.js new file mode 100644 index 0000000000..3df2ca488d --- /dev/null +++ b/modules/es/bases/es.DocumentLeafNode.js @@ -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; +}; diff --git a/modules/es/bases/es.DocumentModelBranchNode.js b/modules/es/bases/es.DocumentModelBranchNode.js index 28593e67b8..faccc97966 100644 --- a/modules/es/bases/es.DocumentModelBranchNode.js +++ b/modules/es/bases/es.DocumentModelBranchNode.js @@ -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 ); diff --git a/modules/es/bases/es.DocumentModelLeafNode.js b/modules/es/bases/es.DocumentModelLeafNode.js index 06e64fdd46..bb7545da80 100644 --- a/modules/es/bases/es.DocumentModelLeafNode.js +++ b/modules/es/bases/es.DocumentModelLeafNode.js @@ -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 ); diff --git a/modules/es/bases/es.DocumentNode.js b/modules/es/bases/es.DocumentNode.js index d8f781e583..5689ad0f61 100644 --- a/modules/es/bases/es.DocumentNode.js +++ b/modules/es/bases/es.DocumentNode.js @@ -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 ); diff --git a/modules/es/bases/es.DocumentViewBranchNode.js b/modules/es/bases/es.DocumentViewBranchNode.js index 759aea7401..9e4f0cfebe 100644 --- a/modules/es/bases/es.DocumentViewBranchNode.js +++ b/modules/es/bases/es.DocumentViewBranchNode.js @@ -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 ); diff --git a/modules/es/bases/es.DocumentViewLeafNode.js b/modules/es/bases/es.DocumentViewLeafNode.js index 99ff366254..6dea945a44 100644 --- a/modules/es/bases/es.DocumentViewLeafNode.js +++ b/modules/es/bases/es.DocumentViewLeafNode.js @@ -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 ); diff --git a/tests/es/index.html b/tests/es/index.html index c0872c5567..abdaf8bd01 100644 --- a/tests/es/index.html +++ b/tests/es/index.html @@ -23,8 +23,9 @@ - + +