r101887 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r101886‎ | r101887 | r101888 >
Date:21:48, 3 November 2011
Author:tparscal
Status:deferred
Tags:
Comment:
Reorganized model nodes into branches and leafs
Modified paths:
  • /trunk/extensions/VisualEditor/demo/index.html (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentBranchNode.js (added) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelBranchNode.js (added) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelLeafNode.js (added) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelNode.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewBranchNode.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewLeafNode.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewNode.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/bases/es.EventEmitter.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.HeadingModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.ListItemModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.ListModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.ParagraphModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.TableCellModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.TableModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/models/es.TableRowModel.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/es/serializers/es.HtmlSerializer.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/es.DocumentModel.test.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/es.DocumentNode.test.js (modified) (history)
  • /trunk/extensions/VisualEditor/tests/index.html (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/tests/es.DocumentNode.test.js
@@ -1,9 +1,12 @@
22 module( 'es/bases' );
33
44 function DocumentNodeStub( items, name, size ) {
 5+ // Inheritance
 6+ es.DocumentBranchNode.call( this, items );
 7+
 8+ // Properties
59 this.name = name;
610 this.size = size;
7 - return es.extendObject( new es.DocumentNode( items ), this );
811 }
912
1013 DocumentNodeStub.prototype.getContentLength = function() {
@@ -15,6 +18,8 @@
1619 return this.size + 2;
1720 };
1821
 22+es.extendClass( DocumentNodeStub, es.DocumentBranchNode );
 23+
1924 test( 'es.DocumentNode', function() {
2025
2126 // Stub test
Index: trunk/extensions/VisualEditor/tests/index.html
@@ -12,14 +12,22 @@
1313 <h2 id="qunit-userAgent"></h2>
1414 <ol id="qunit-tests"></ol>
1515 <div id="qunit-fixture">test markup</div>
 16+
 17+ <!-- EditSurface -->
1618 <script src="../modules/jquery/jquery.js"></script>
1719 <script src="../modules/qunit/qunit.js"></script>
1820 <script src="../modules/es/es.js"></script>
1921 <script src="../modules/es/es.Range.js"></script>
2022 <script src="../modules/es/es.Transaction.js"></script>
 23+
 24+ <!-- Bases -->
2125 <script src="../modules/es/bases/es.EventEmitter.js"></script>
22 - <script src="../modules/es/bases/es.DocumentNode.js"></script>
 26+ <script src="../modules/es/bases/es.DocumentBranchNode.js"></script>
2327 <script src="../modules/es/bases/es.DocumentModelNode.js"></script>
 28+ <script src="../modules/es/bases/es.DocumentModelBranchNode.js"></script>
 29+ <script src="../modules/es/bases/es.DocumentModelLeafNode.js"></script>
 30+
 31+ <!-- Models -->
2432 <script src="../modules/es/models/es.DocumentModel.js"></script>
2533 <script src="../modules/es/models/es.ListItemModel.js"></script>
2634 <script src="../modules/es/models/es.ListModel.js"></script>
@@ -27,6 +35,8 @@
2836 <script src="../modules/es/models/es.TableCellModel.js"></script>
2937 <script src="../modules/es/models/es.TableModel.js"></script>
3038 <script src="../modules/es/models/es.TableRowModel.js"></script>
 39+
 40+ <!-- Tests -->
3141 <script src="es.test.js"></script>
3242 <script src="es.DocumentNode.test.js"></script>
3343 <script src="es.DocumentModel.test.js"></script>
Index: trunk/extensions/VisualEditor/tests/es.DocumentModel.test.js
@@ -224,9 +224,14 @@
225225 console.log( 'mismatched content lengths', a[i], b[i] );
226226 return false;
227227 }
228 - if ( !equalLengths( a[i].getChildren(), b[i].getChildren() ) ) {
 228+ aIsBranch = typeof a[i].getChildren === 'function';
 229+ bIsBranch = typeof b[i].getChildren === 'function';
 230+ if ( aIsBranch !== bIsBranch ) {
229231 return false;
230232 }
 233+ if ( aIsBranch && !equalLengths( a[i].getChildren(), b[i].getChildren() ) ) {
 234+ return false;
 235+ }
231236 }
232237 return true;
233238 }
@@ -891,13 +896,17 @@
892897
893898 test( 'es.DocumentDocumentModelNode child operations', 20, function() {
894899 // Example data (integers) is used for simplicity of testing
895 - var node1 = new es.DocumentModelNode( '1' ),
896 - node2 = new es.DocumentModelNode( '2' ),
897 - node3 = new es.DocumentModelNode( '3', null, [new es.DocumentModelNode( '3a' )] ),
898 - node4 = new es.DocumentModelNode(
 900+ var node1 = new es.DocumentModelBranchNode( '1' ),
 901+ node2 = new es.DocumentModelBranchNode( '2' ),
 902+ node3 = new es.DocumentModelBranchNode(
 903+ '3',
 904+ null,
 905+ [new es.DocumentModelBranchNode( '3a' )]
 906+ ),
 907+ node4 = new es.DocumentModelBranchNode(
899908 '4',
900909 null,
901 - [new es.DocumentModelNode( '4a' ), new es.DocumentModelNode( '4b' )]
 910+ [new es.DocumentModelBranchNode( '4a' ), new es.DocumentModelBranchNode( '4b' )]
902911 );
903912
904913 // Event triggering is detected using a callback that increments a counter
Index: trunk/extensions/VisualEditor/demo/index.html
@@ -68,8 +68,10 @@
6969
7070 <!-- Bases -->
7171 <script src="../modules/es/bases/es.EventEmitter.js"></script>
72 - <script src="../modules/es/bases/es.DocumentNode.js"></script>
 72+ <script src="../modules/es/bases/es.DocumentBranchNode.js"></script>
7373 <script src="../modules/es/bases/es.DocumentModelNode.js"></script>
 74+ <script src="../modules/es/bases/es.DocumentModelBranchNode.js"></script>
 75+ <script src="../modules/es/bases/es.DocumentModelLeafNode.js"></script>
7476 <script src="../modules/es/bases/es.DocumentViewNode.js"></script>
7577 <script src="../modules/es/bases/es.DocumentViewBranchNode.js"></script>
7678 <script src="../modules/es/bases/es.DocumentViewLeafNode.js"></script>
Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js
@@ -6,17 +6,18 @@
77 *
88 * @class
99 * @constructor
10 - * @extends {es.DocumentModelNode}
 10+ * @extends {es.DocumentModelBranchNode}
1111 * @param {Array} data Model data to initialize with, such as data from es.DocumentModel.getData()
1212 * @param {Object} attributes Document attributes
1313 */
1414 es.DocumentModel = function( data, attributes ) {
1515 // Inheritance
16 - es.DocumentModelNode.call( this, 'document', null, data ? data.length : 0 );
17 -
 16+ es.DocumentModelBranchNode.call( this, 'document', null );
 17+
1818 // Properties
1919 this.data = es.isArray( data ) ? data : [];
2020 this.attributes = es.isPlainObject( attributes ) ? attributes : {};
 21+ this.contentLength = this.data.length;
2122
2223 // Auto-generate model tree
2324 var nodes = es.DocumentModel.createNodesFromData( this.data );
@@ -273,7 +274,7 @@
274275 * it's child nodes.
275276 */
276277 es.DocumentModel.createNodesFromData = function( data ) {
277 - var currentNode = new es.DocumentModelNode();
 278+ var currentNode = new es.DocumentModelBranchNode();
278279 for ( var i = 0, length = data.length; i < length; i++ ) {
279280 if ( data[i].type !== undefined ) {
280281 // It's an element, figure out it's type
@@ -290,7 +291,7 @@
291292 throw 'Unsuported element error. No class registered for element type: ' + type;
292293 }
293294 // Create a model node for the element
294 - var newNode = new es.DocumentModel.nodeModels[element.type]( element );
 295+ var newNode = new es.DocumentModel.nodeModels[element.type]( element, 0 );
295296 // Add the new model node as a child
296297 currentNode.push( newNode );
297298 // Descend into the new model node
@@ -1251,4 +1252,4 @@
12521253
12531254 /* Inheritance */
12541255
1255 -es.extendClass( es.DocumentModel, es.DocumentModelNode );
 1256+es.extendClass( es.DocumentModel, es.DocumentModelBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.HeadingModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelLeafNode}
88 * @param {Object} element Document data element of this node
99 * @param {Integer} length Length of document data element
1010 */
1111 es.HeadingModel = function( element, length ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'heading', element, length );
 13+ es.DocumentModelLeafNode.call( this, 'heading', element, length );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.HeadingModel, es.DocumentModelNode );
 39+es.extendClass( es.HeadingModel, es.DocumentModelLeafNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.TableRowModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelBranchNode}
88 * @param {Object} element Document data element of this node
99 * @param {es.DocumentModelNode[]} contents List of child nodes to initially add
1010 */
1111 es.TableRowModel = function( element, contents ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'tableRow', element, contents );
 13+ es.DocumentModelBranchNode.call( this, 'tableRow', element, contents );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.TableRowModel, es.DocumentModelNode );
 39+es.extendClass( es.TableRowModel, es.DocumentModelBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.ParagraphModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelLeafNode}
88 * @param {Object} element Document data element of this node
99 * @param {Integer} length Length of document data element
1010 */
1111 es.ParagraphModel = function( element, length ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'paragraph', element, length );
 13+ es.DocumentModelLeafNode.call( this, 'paragraph', element, length );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.ParagraphModel, es.DocumentModelNode );
 39+es.extendClass( es.ParagraphModel, es.DocumentModelLeafNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.TableCellModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelBranchNode}
88 * @param {Object} element Document data element of this node
99 * @param {es.DocumentModelNode[]} contents List of child nodes to initially add
1010 */
1111 es.TableCellModel = function( element, contents ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'tableCell', element, contents );
 13+ es.DocumentModelBranchNode.call( this, 'tableCell', element, contents );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.TableCellModel, es.DocumentModelNode );
 39+es.extendClass( es.TableCellModel, es.DocumentModelBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.TableModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelBranchNode}
88 * @param {Object} element Document data element of this node
9 - * @param {es.DocumentModelNode[]} contents List of child nodes to initially add
 9+ * @param {es.TableCellModel[]} contents List of child nodes to initially add
1010 */
1111 es.TableModel = function( element, contents ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'table', element, contents );
 13+ es.DocumentModelBranchNode.call( this, 'table', element, contents );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.TableModel, es.DocumentModelNode );
 39+es.extendClass( es.TableModel, es.DocumentModelBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.ListItemModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelLeafNode}
88 * @param {Object} element Document data element of this node
99 * @param {Integer} length Length of document data element
1010 */
1111 es.ListItemModel = function( element, length ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'listItem', element, length );
 13+ es.DocumentModelLeafNode.call( this, 'listItem', element, length );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.ListItemModel, es.DocumentModelNode );
 39+es.extendClass( es.ListItemModel, es.DocumentModelLeafNode );
Index: trunk/extensions/VisualEditor/modules/es/models/es.ListModel.js
@@ -3,13 +3,13 @@
44 *
55 * @class
66 * @constructor
7 - * @extends {es.DocumentModelNode}
 7+ * @extends {es.DocumentModelBranchNode}
88 * @param {Object} element Document data element of this node
9 - * @param {es.DocumentModelNode[]} contents List of child nodes to initially add
 9+ * @param {es.ListItemModel[]} contents List of child nodes to initially add
1010 */
1111 es.ListModel = function( element, contents ) {
1212 // Inheritance
13 - es.DocumentModelNode.call( this, 'list', element, contents );
 13+ es.DocumentModelBranchNode.call( this, 'list', element, contents );
1414 };
1515
1616 /* Methods */
@@ -35,4 +35,4 @@
3636
3737 /* Inheritance */
3838
39 -es.extendClass( es.ListModel, es.DocumentModelNode );
 39+es.extendClass( es.ListModel, es.DocumentModelBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/serializers/es.HtmlSerializer.js
@@ -39,18 +39,15 @@
4040 };
4141
4242 es.HtmlSerializer.getExpandedListItems = function( node ) {
43 - var styles,
44 - levels = [];
45 - for ( var i = 0; i < this.children.length; i++ ) {
46 - styles = this.children[i].model.getElementAttribute( 'styles' );
47 - levels = levels.slice( 0, styles.length );
48 - if ( styles[styles.length - 1] === 'number' ) {
49 - if ( !levels[styles.length - 1] ) {
50 - levels[styles.length - 1] = 0;
51 - }
52 - this.children[i].setNumber( ++levels[styles.length - 1] );
53 - }
 43+ var childNode,
 44+ styles,
 45+ tree = [];
 46+ for ( var i = 0; i < node.children.length; i++ ) {
 47+ childNode = node.children[i];
 48+ styles = childNode.attributes.styles;
 49+ // TODO: Build tree from items
5450 }
 51+ return [];
5552 };
5653
5754 /* Methods */
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewNode.js
@@ -9,8 +9,8 @@
1010 * Child objects must extend es.DocumentViewNode.
1111 *
1212 * @class
 13+ * @abstract
1314 * @constructor
14 - * @extends {es.DocumentNode}
1515 * @extends {es.EventEmitter}
1616 * @param model {es.ModelNode} Model to observe
1717 * @param {jQuery} [$element=New DIV element] Element to use as a container
@@ -19,12 +19,10 @@
2020 */
2121 es.DocumentViewNode = function( model, $element ) {
2222 // Inheritance
23 - es.DocumentNode.call( this );
2423 es.EventEmitter.call( this );
2524
2625 // Properties
2726 this.model = model;
28 - this.children = [];
2927 this.$ = $element || $( '<div/>' );
3028
3129 // Reusable function for passing update events upstream
@@ -32,128 +30,8 @@
3331 this.emitUpdate = function() {
3432 _this.emit( 'update' );
3533 };
36 -
37 - if ( model ) {
38 - // Append existing model children
39 - var childModels = model.getChildren();
40 - for ( var i = 0; i < childModels.length; i++ ) {
41 - this.onAfterPush( childModels[i] );
42 - }
43 -
44 - // Observe and mimic changes on model
45 - this.addListenerMethods( this, {
46 - 'afterPush': 'onAfterPush',
47 - 'afterUnshift': 'onAfterUnshift',
48 - 'afterPop': 'onAfterPop',
49 - 'afterShift': 'onAfterShift',
50 - 'afterSplice': 'onAfterSplice',
51 - 'afterSort': 'onAfterSort',
52 - 'afterReverse': 'onAfterReverse'
53 - } );
54 - }
5534 };
5635
57 -es.DocumentViewNode.prototype.onAfterPush = function( childModel ) {
58 - var childView = childModel.createView();
59 - this.emit( 'beforePush', childView );
60 - childView.attach( this );
61 - childView.on( 'update', this.emitUpdate );
62 - // Update children
63 - this.children.push( childView );
64 - // Update DOM
65 - this.$.append( childView.$ );
66 - this.emit( 'afterPush', childView );
67 - this.emit( 'update' );
68 -};
69 -
70 -es.DocumentViewNode.prototype.onAfterUnshift = function( childModel ) {
71 - var childView = childModel.createView();
72 - this.emit( 'beforeUnshift', childView );
73 - childView.attach( this );
74 - childView.on( 'update', this.emitUpdate );
75 - // Update children
76 - this.children.unshift( childView );
77 - // Update DOM
78 - this.$.prepend( childView.$ );
79 - this.emit( 'afterUnshift', childView );
80 - this.emit( 'update' );
81 -};
82 -
83 -es.DocumentViewNode.prototype.onAfterPop = function() {
84 - this.emit( 'beforePop' );
85 - // Update children
86 - var childView = this.children.pop();
87 - childView.detach();
88 - childView.removeEventListener( 'update', this.emitUpdate );
89 - // Update DOM
90 - childView.$.detach();
91 - this.emit( 'afterPop' );
92 - this.emit( 'update' );
93 -};
94 -
95 -es.DocumentViewNode.prototype.onAfterShift = function() {
96 - this.emit( 'beforeShift' );
97 - // Update children
98 - var childView = this.children.shift();
99 - childView.detach();
100 - childView.removeEventListener( 'update', this.emitUpdate );
101 - // Update DOM
102 - childView.$.detach();
103 - this.emit( 'afterShift' );
104 - this.emit( 'update' );
105 -};
106 -
107 -es.DocumentViewNode.prototype.onAfterSplice = function( index, howmany ) {
108 - var args = Array.prototype.slice( arguments, 0 );
109 - this.emit.apply( ['beforeSplice'].concat( args ) );
110 - // Update children
111 - this.splice.apply( this, args );
112 - // Update DOM
113 - this.$.children()
114 - // Removals
115 - .slice( index, index + howmany )
116 - .detach()
117 - .end()
118 - // Insertions
119 - .get( index )
120 - .after( $.map( args.slice( 2 ), function( childView ) {
121 - return childView.$;
122 - } ) );
123 - this.emit.apply( ['afterSplice'].concat( args ) );
124 - this.emit( 'update' );
125 -};
126 -
127 -es.DocumentViewNode.prototype.onAfterSort = function() {
128 - this.emit( 'beforeSort' );
129 - var childModels = this.model.getChildren();
130 - for ( var i = 0; i < childModels.length; i++ ) {
131 - for ( var j = 0; j < this.children.length; j++ ) {
132 - if ( this.children[j].getModel() === childModels[i] ) {
133 - var childView = this.children[j];
134 - // Update children
135 - this.children.splice( j, 1 );
136 - this.children.push( childView );
137 - // Update DOM
138 - this.$.append( childView.$ );
139 - }
140 - }
141 - }
142 - this.emit( 'afterSort' );
143 - this.emit( 'update' );
144 -};
145 -
146 -es.DocumentViewNode.prototype.onAfterReverse = function() {
147 - this.emit( 'beforeReverse' );
148 - // Update children
149 - this.reverse();
150 - // Update DOM
151 - this.$.children().each( function() {
152 - $(this).prependTo( $(this).parent() );
153 - } );
154 - this.emit( 'afterReverse' );
155 - this.emit( 'update' );
156 -};
157 -
15836 /**
15937 * Gets a reference to the model this node observes.
16038 *
@@ -220,5 +98,4 @@
22199
222100 /* Inheritance */
223101
224 -es.extendClass( es.DocumentViewNode, es.DocumentNode );
225102 es.extendClass( es.DocumentViewNode, es.EventEmitter );
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentBranchNode.js
@@ -0,0 +1,233 @@
 2+/**
 3+ * Creates an es.DocumentBranchNode object.
 4+ *
 5+ * @class
 6+ * @abstract
 7+ * @constructor
 8+ * @param {es.DocumentBranchNode[]} nodes List of document nodes to initially add
 9+ */
 10+es.DocumentBranchNode = function( nodes ) {
 11+ this.children = es.isArray( nodes ) ? nodes : [];
 12+};
 13+
 14+/* Methods */
 15+
 16+/**
 17+ * Gets a list of child nodes.
 18+ *
 19+ * @abstract
 20+ * @method
 21+ * @returns {es.DocumentBranchNode[]} List of document nodes
 22+ */
 23+es.DocumentBranchNode.prototype.getChildren = function() {
 24+ return this.children;
 25+};
 26+
 27+/**
 28+ * Gets the range within this node that a given child node covers.
 29+ *
 30+ * @method
 31+ * @param {es.ModelNode} node Node to get range for
 32+ * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes
 33+ * @returns {es.Range|null} Range of node or null if node was not found
 34+ */
 35+es.DocumentBranchNode.prototype.getRangeFromNode = function( node, shallow ) {
 36+ if ( this.children.length ) {
 37+ var isBranch;
 38+ for ( var i = 0, length = this.children.length, left = 0; i < length; i++ ) {
 39+ if ( this.children[i] === node ) {
 40+ return new es.Range( left, left + this.children[i].getElementLength() );
 41+ }
 42+ isBranch = typeof this.children[i].getChildren === 'function';
 43+ if ( !shallow && isBranch && this.children[i].getChildren().length ) {
 44+ var range = this.children[i].getRangeFromNode( node );
 45+ if ( range !== null ) {
 46+ // Include opening of parent
 47+ left++;
 48+ return es.Range.newFromTranslatedRange( range, left );
 49+ }
 50+ }
 51+ left += this.children[i].getElementLength();
 52+ }
 53+ }
 54+ return null;
 55+};
 56+
 57+/**
 58+ * Gets the content offset of a node.
 59+ *
 60+ * This method is pretty expensive. If you need to get different slices of the same content, get
 61+ * the content first, then slice it up locally.
 62+ *
 63+ * TODO: Rewrite this method to not use recursion, because the function call overhead is expensive
 64+ *
 65+ * @method
 66+ * @param {es.DocumentModelNode} node Node to get offset of
 67+ * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes
 68+ * @returns {Integer} Offset of node or -1 of node was not found
 69+ */
 70+es.DocumentBranchNode.prototype.getOffsetFromNode = function( node, shallow ) {
 71+ if ( this.children.length ) {
 72+ var offset = 0,
 73+ isBranch;
 74+ for ( var i = 0, length = this.children.length; i < length; i++ ) {
 75+ if ( this.children[i] === node ) {
 76+ return offset;
 77+ }
 78+ isBranch = typeof this.children[i].getChildren === 'function';
 79+ if ( !shallow && isBranch && this.children[i].getChildren().length ) {
 80+ var childOffset = this.getOffsetFromNode.call( this.children[i], node );
 81+ if ( childOffset !== -1 ) {
 82+ return offset + 1 + childOffset;
 83+ }
 84+ }
 85+ offset += this.children[i].getElementLength();
 86+ }
 87+ }
 88+ return -1;
 89+};
 90+
 91+/**
 92+ * Gets the node at a given offset.
 93+ *
 94+ * This method is pretty expensive. If you need to get different slices of the same content, get
 95+ * the content first, then slice it up locally.
 96+ *
 97+ * TODO: Rewrite this method to not use recursion, because the function call overhead is expensive
 98+ *
 99+ * @method
 100+ * @param {Integer} offset Offset get node for
 101+ * @param {Boolean} [shallow] Do not iterate into child nodes of child nodes
 102+ * @returns {es.DocumentModelNode|null} Node at offset, or null if non was found
 103+ */
 104+es.DocumentBranchNode.prototype.getNodeFromOffset = function( offset, shallow ) {
 105+ if ( this.children.length ) {
 106+ var nodeOffset = 0,
 107+ nodeLength,
 108+ isBranch;
 109+ for ( var i = 0, length = this.children.length; i < length; i++ ) {
 110+ nodeLength = this.children[i].getElementLength();
 111+ if ( offset >= nodeOffset && offset < nodeOffset + nodeLength ) {
 112+ isBranch = typeof this.children[i].getChildren === 'function';
 113+ if ( !shallow && isBranch && this.children[i].getChildren().length ) {
 114+ return this.getNodeFromOffset.call( this.children[i], offset - nodeOffset - 1 );
 115+ } else {
 116+ return this.children[i];
 117+ }
 118+ }
 119+ nodeOffset += nodeLength;
 120+ }
 121+ }
 122+ return null;
 123+};
 124+
 125+/**
 126+ * Gets a list of nodes and their sub-ranges which are covered by a given range.
 127+ *
 128+ * @method
 129+ * @param {es.Range} range Range to select nodes within
 130+ * @param {Boolean} [shallow] Do not recurse into child nodes of child nodes
 131+ * @returns {Array} List of objects with 'node' and 'range' properties describing nodes which are
 132+ * covered by the range and the range within the node that is covered
 133+ */
 134+es.DocumentBranchNode.prototype.selectNodes = function( range, shallow ) {
 135+ if ( typeof range === 'undefined' ) {
 136+ range = new es.Range( 0, this.model.getContentLength() );
 137+ } else {
 138+ range.normalize();
 139+ }
 140+ var nodes = [],
 141+ i,
 142+ left,
 143+ right,
 144+ start = range.start,
 145+ end = range.end,
 146+ startInside,
 147+ endInside;
 148+
 149+ if ( start < 0 ) {
 150+ throw 'The start offset of the range is negative';
 151+ }
 152+
 153+ if ( this.children.length === 0 ) {
 154+ // Special case: this node doesn't have any children
 155+ // The return value is simply the range itself, if it is not out of bounds
 156+ if ( end > this.getContentLength() ) {
 157+ throw 'The end offset of the range is past the end of the node';
 158+ }
 159+ return [{ 'node': this, 'range': new es.Range( start, end ) }];
 160+ }
 161+
 162+ // This node has children, loop over them
 163+ left = 1; // First offset inside the first child. Offset 0 is before the first child
 164+ for ( i = 0; i < this.children.length; i++ ) {
 165+ // left <= any offset inside this.children[i] <= right
 166+ right = left + this.children[i].getContentLength();
 167+
 168+ if ( start == end && ( start == left - 1 || start == right + 1 ) ) {
 169+ // Empty range outside of any node
 170+ return [];
 171+ }
 172+ if ( start == left - 1 && end == right + 1 ) {
 173+ // The range covers the entire node, including its opening and closing elements
 174+ return [ { 'node': this.children[i] } ];
 175+ }
 176+ if ( start == left - 1 ) {
 177+ // start is between this.children[i-1] and this.children[i], move it to left for convenience
 178+ // We don't need to check for start < end here because we already have start != end and
 179+ // start <= end
 180+ start = left;
 181+ }
 182+ if ( end == right + 1 ) {
 183+ // end is between this.children[i] and this.children[i+1], move it to right for convenience
 184+ // We don't need to check for start < end here because we already have start != end and
 185+ // start <= end
 186+ end = right;
 187+ }
 188+
 189+ startInside = start >= left && start <= right; // is the start inside this.children[i]?
 190+ endInside = end >= left && end <= right; // is the end inside this.children[i]?
 191+
 192+ if ( startInside && endInside ) {
 193+ // The range is entirely inside this.children[i]
 194+ if ( shallow ) {
 195+ nodes = [{ 'node': this.children[i], 'range': new es.Range( start - left, end - left ) }];
 196+ } else {
 197+ // Recurse into this.children[i]
 198+ nodes = this.children[i].selectNodes( new es.Range( start - left, end - left ) );
 199+ }
 200+ // Since the start and end are both inside this.children[i], we know for sure that we're done, so
 201+ // return
 202+ return nodes;
 203+ } else if ( startInside ) {
 204+ // The start is inside this.children[i] but the end isn't
 205+ // Add a range from the start of the range to the end of this.children[i]
 206+ nodes.push( { 'node': this.children[i], 'range': new es.Range( start - left, right - left ) } );
 207+ } else if ( endInside ) {
 208+ // The end is inside this.children[i] but the start isn't
 209+ // Add a range from the start of this.children[i] to the end of the range
 210+ nodes.push( { 'node': this.children[i], 'range': new es.Range( 0, end - left ) } );
 211+ // We've found the end, so we're done
 212+ return nodes;
 213+ } else if ( nodes.length > 0 ) {
 214+ // Neither the start nor the end is inside this.children[i], but nodes is non-empty,
 215+ // so this.children[i] must be between the start and the end
 216+ // Add the entire node, so no range property
 217+ nodes.push( { 'node': this.children[i] } );
 218+ }
 219+
 220+ // Move left to the start of this.children[i+1] for the next iteration
 221+ // +2 because we need to jump over the offset between this.children[i] and this.children[i+1]
 222+ left = right + 2;
 223+ }
 224+
 225+ // If we got here, that means that at least some part of the range is out of bounds
 226+ // This is an error
 227+ if ( nodes.length === 0 ) {
 228+ throw 'The start offset of the range is past the end of the node';
 229+ } else {
 230+ // Apparently the start was inside this node, but the end wasn't
 231+ throw 'The end offset of the range is past the end of the node';
 232+ }
 233+ return nodes;
 234+};
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelBranchNode.js
@@ -0,0 +1,230 @@
 2+/**
 3+ * Creates an es.DocumentModelBranchNode object.
 4+ *
 5+ * @class
 6+ * @abstract
 7+ * @constructor
 8+ * @extends {es.DocumentModelNode}
 9+ * @extends {es.DocumentBranchNode}
 10+ * @param {String} type Symbolic name of node type
 11+ * @param {es.DocumentModelBranchNode[]} contents List of child nodes to append
 12+ */
 13+es.DocumentModelBranchNode = function( type, element, contents ) {
 14+ // Inheritance
 15+ es.DocumentModelNode.call( this, type, element, 0 );
 16+ es.DocumentBranchNode.call( this );
 17+
 18+ // Child nodes
 19+ if ( es.isArray( contents ) ) {
 20+ for ( var i = 0; i < contents.length; i++ ) {
 21+ this.push( contents[i] );
 22+ }
 23+ }
 24+};
 25+
 26+/* Methods */
 27+
 28+/**
 29+ * Gets a plain object representation of the document's data.
 30+ *
 31+ * The resulting object is compatible with es.DocumentModel.newFromPlainObject.
 32+ *
 33+ * @method
 34+ * @returns {Object} Plain object representation
 35+ */
 36+es.DocumentModelBranchNode.prototype.getPlainObject = function() {
 37+ var obj = { 'type': this.type };
 38+ if ( this.element && this.element.attributes ) {
 39+ obj.attributes = es.copyObject( this.element.attributes );
 40+ }
 41+ obj.children = [];
 42+ for ( var i = 0; i < this.children.length; i++ ) {
 43+ obj.children.push( this.children[i].getPlainObject() );
 44+ }
 45+ return obj;
 46+};
 47+
 48+/**
 49+ * Adds a node to the end of this node's children.
 50+ *
 51+ * @method
 52+ * @param {es.DocumentModelBranchNode} childModel Item to add
 53+ * @returns {Integer} New number of children
 54+ * @emits beforePush (childModel)
 55+ * @emits afterPush (childModel)
 56+ * @emits update
 57+ */
 58+es.DocumentModelBranchNode.prototype.push = function( childModel ) {
 59+ this.emit( 'beforePush', childModel );
 60+ childModel.attach( this );
 61+ childModel.on( 'update', this.emitUpdate );
 62+ this.children.push( childModel );
 63+ this.adjustContentLength( childModel.getElementLength(), true );
 64+ this.emit( 'afterPush', childModel );
 65+ this.emit( 'update' );
 66+ return this.children.length;
 67+};
 68+
 69+/**
 70+ * Adds a node to the beginning of this node's children.
 71+ *
 72+ * @method
 73+ * @param {es.DocumentModelBranchNode} childModel Item to add
 74+ * @returns {Integer} New number of children
 75+ * @emits beforeUnshift (childModel)
 76+ * @emits afterUnshift (childModel)
 77+ * @emits update
 78+ */
 79+es.DocumentModelBranchNode.prototype.unshift = function( childModel ) {
 80+ this.emit( 'beforeUnshift', childModel );
 81+ childModel.attach( this );
 82+ childModel.on( 'update', this.emitUpdate );
 83+ this.children.unshift( childModel );
 84+ this.adjustContentLength( childModel.getElementLength(), true );
 85+ this.emit( 'afterUnshift', childModel );
 86+ this.emit( 'update' );
 87+ return this.children.length;
 88+};
 89+
 90+/**
 91+ * Removes a node from the end of this node's children
 92+ *
 93+ * @method
 94+ * @returns {es.DocumentModelBranchNode} Removed childModel
 95+ * @emits beforePop
 96+ * @emits afterPop
 97+ * @emits update
 98+ */
 99+es.DocumentModelBranchNode.prototype.pop = function() {
 100+ if ( this.children.length ) {
 101+ this.emit( 'beforePop' );
 102+ var childModel = this.children[this.children.length - 1];
 103+ childModel.detach();
 104+ childModel.removeListener( 'update', this.emitUpdate );
 105+ this.children.pop();
 106+ this.adjustContentLength( -childModel.getElementLength(), true );
 107+ this.emit( 'afterPop' );
 108+ this.emit( 'update' );
 109+ return childModel;
 110+ }
 111+};
 112+
 113+/**
 114+ * Removes a node from the beginning of this node's children
 115+ *
 116+ * @method
 117+ * @returns {es.DocumentModelBranchNode} Removed childModel
 118+ * @emits beforeShift
 119+ * @emits afterShift
 120+ * @emits update
 121+ */
 122+es.DocumentModelBranchNode.prototype.shift = function() {
 123+ if ( this.children.length ) {
 124+ this.emit( 'beforeShift' );
 125+ var childModel = this.children[0];
 126+ childModel.detach();
 127+ childModel.removeListener( 'update', this.emitUpdate );
 128+ this.children.shift();
 129+ this.adjustContentLength( -childModel.getElementLength(), true );
 130+ this.emit( 'afterShift' );
 131+ this.emit( 'update' );
 132+ return childModel;
 133+ }
 134+};
 135+
 136+/**
 137+ * Adds and removes nodes from this node's children.
 138+ *
 139+ * @method
 140+ * @param {Integer} index Index to remove and or insert nodes at
 141+ * @param {Integer} howmany Number of nodes to remove
 142+ * @param {es.DocumentModelBranchNode} [...] Variadic list of nodes to insert
 143+ * @returns {es.DocumentModelBranchNode[]} Removed nodes
 144+ * @emits beforeSplice (index, howmany, [...])
 145+ * @emits afterSplice (index, howmany, [...])
 146+ * @emits update
 147+ */
 148+es.DocumentModelBranchNode.prototype.splice = function( index, howmany ) {
 149+ var i,
 150+ length,
 151+ args = Array.prototype.slice.call( arguments, 0 ),
 152+ diff = 0;
 153+ this.emit.apply( this, ['beforeSplice'].concat( args ) );
 154+ if ( args.length >= 3 ) {
 155+ for ( i = 2, length = args.length; i < length; i++ ) {
 156+ diff += args[i].getElementLength();
 157+ args[i].attach( this );
 158+ }
 159+ }
 160+ var removed = this.children.splice.apply( this.children, args );
 161+ for ( i = 0, length = removed.length; i < length; i++ ) {
 162+ diff -= removed[i].getElementLength();
 163+ removed[i].detach();
 164+ removed[i].removeListener( 'update', this.emitUpdate );
 165+ }
 166+ this.adjustContentLength( diff, true );
 167+ this.emit.apply( this, ['afterSplice'].concat( args ) );
 168+ this.emit( 'update' );
 169+ return removed;
 170+};
 171+
 172+/**
 173+ * Sorts this node's children.
 174+ *
 175+ * @method
 176+ * @param {Function} sortfunc Function to use when sorting
 177+ * @emits beforeSort (sortfunc)
 178+ * @emits afterSort (sortfunc)
 179+ * @emits update
 180+ */
 181+es.DocumentModelBranchNode.prototype.sort = function( sortfunc ) {
 182+ this.emit( 'beforeSort', sortfunc );
 183+ this.children.sort( sortfunc );
 184+ this.emit( 'afterSort', sortfunc );
 185+ this.emit( 'update' );
 186+};
 187+
 188+/**
 189+ * Reverses the order of this node's children.
 190+ *
 191+ * @method
 192+ * @emits beforeReverse
 193+ * @emits afterReverse
 194+ * @emits update
 195+ */
 196+es.DocumentModelBranchNode.prototype.reverse = function() {
 197+ this.emit( 'beforeReverse' );
 198+ this.children.reverse();
 199+ this.emit( 'afterReverse' );
 200+ this.emit( 'update' );
 201+};
 202+
 203+/**
 204+ * Sets the root node to this and all of it's children.
 205+ *
 206+ * @method
 207+ * @param {es.DocumentModelNode} root Node to use as root
 208+ */
 209+es.DocumentModelBranchNode.prototype.setRoot = function( root ) {
 210+ this.root = root;
 211+ for ( var i = 0; i < this.children.length; i++ ) {
 212+ this.children[i].setRoot( root );
 213+ }
 214+};
 215+
 216+/**
 217+ * Clears the root node from this and all of it's children.
 218+ *
 219+ * @method
 220+ */
 221+es.DocumentModelBranchNode.prototype.clearRoot = function() {
 222+ this.root = null;
 223+ for ( var i = 0; i < this.children.length; i++ ) {
 224+ this.children[i].clearRoot();
 225+ }
 226+};
 227+
 228+/* Inheritance */
 229+
 230+es.extendClass( es.DocumentModelBranchNode, es.DocumentModelNode );
 231+es.extendClass( es.DocumentModelBranchNode, es.DocumentBranchNode );
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelNode.js
@@ -1,20 +1,16 @@
22 /**
33 * Creates an es.DocumentModelNode object.
44 *
5 - * es.DocumentModelNode is a simple wrapper around es.ModelNode, which adds functionality for model
6 - * nodes to be used as nodes in a space partitioning tree.
7 - *
85 * @class
96 * @abstract
107 * @constructor
11 - * @extends {es.DocumentNode}
128 * @extends {es.EventEmitter}
 9+ * @param {String} type Symbolic name of node type
1310 * @param {Integer|Array} contents Either Length of content or array of child nodes to append
1411 * @property {Integer} contentLength Length of content
1512 */
16 -es.DocumentModelNode = function( type, element, contents ) {
 13+es.DocumentModelNode = function( type, element, length ) {
1714 // Inheritance
18 - es.DocumentNode.call( this );
1915 es.EventEmitter.call( this );
2016
2117 // Reusable function for passing update events upstream
@@ -28,17 +24,7 @@
2925 this.parent = null;
3026 this.root = this;
3127 this.element = element || null;
32 - this.contentLength = 0;
33 - if ( typeof contents === 'number' ) {
34 - if ( contents < 0 ) {
35 - throw 'Invalid content length error. Content length can not be less than 0.';
36 - }
37 - this.contentLength = contents;
38 - } else if ( es.isArray( contents ) ) {
39 - for ( var i = 0; i < contents.length; i++ ) {
40 - this.push( contents[i] );
41 - }
42 - }
 28+ this.contentLength = length;
4329 };
4430
4531 /* Abstract Methods */
@@ -54,8 +40,6 @@
5541 throw 'DocumentModelNode.createView not implemented in this subclass:' + this.constructor;
5642 };
5743
58 -/* Methods */
59 -
6044 /**
6145 * Gets a plain object representation of the document's data.
6246 *
@@ -65,177 +49,12 @@
6650 * @returns {Object} Plain object representation
6751 */
6852 es.DocumentModelNode.prototype.getPlainObject = function() {
69 - var obj = { 'type': this.type };
70 - if ( this.element && this.element.attributes ) {
71 - obj.attributes = es.copyObject( this.element.attributes );
72 - }
73 - if ( this.children.length ) {
74 - obj.children = [];
75 - for ( var i = 0; i < this.children.length; i++ ) {
76 - obj.children.push( this.children[i].getPlainObject() );
77 - }
78 - } else if ( this.getContentLength() ) {
79 - obj.content = es.DocumentModel.getExpandedContentData( this.getContent() );
80 - }
81 - return obj;
 53+ throw 'DocumentModelNode.getPlainObject not implemented in this subclass:' + this.constructor;
8254 };
8355
84 -/**
85 - * Adds a node to the end of this node's children.
86 - *
87 - * @method
88 - * @param {es.DocumentModelNode} childModel Item to add
89 - * @returns {Integer} New number of children
90 - * @emits beforePush (childModel)
91 - * @emits afterPush (childModel)
92 - * @emits update
93 - */
94 -es.DocumentModelNode.prototype.push = function( childModel ) {
95 - this.emit( 'beforePush', childModel );
96 - childModel.attach( this );
97 - childModel.on( 'update', this.emitUpdate );
98 - this.children.push( childModel );
99 - this.adjustContentLength( childModel.getElementLength(), true );
100 - this.emit( 'afterPush', childModel );
101 - this.emit( 'update' );
102 - return this.children.length;
103 -};
 56+/* Methods */
10457
10558 /**
106 - * Adds a node to the beginning of this node's children.
107 - *
108 - * @method
109 - * @param {es.DocumentModelNode} childModel Item to add
110 - * @returns {Integer} New number of children
111 - * @emits beforeUnshift (childModel)
112 - * @emits afterUnshift (childModel)
113 - * @emits update
114 - */
115 -es.DocumentModelNode.prototype.unshift = function( childModel ) {
116 - this.emit( 'beforeUnshift', childModel );
117 - childModel.attach( this );
118 - childModel.on( 'update', this.emitUpdate );
119 - this.children.unshift( childModel );
120 - this.adjustContentLength( childModel.getElementLength(), true );
121 - this.emit( 'afterUnshift', childModel );
122 - this.emit( 'update' );
123 - return this.children.length;
124 -};
125 -
126 -/**
127 - * Removes a node from the end of this node's children
128 - *
129 - * @method
130 - * @returns {es.DocumentModelNode} Removed childModel
131 - * @emits beforePop
132 - * @emits afterPop
133 - * @emits update
134 - */
135 -es.DocumentModelNode.prototype.pop = function() {
136 - if ( this.children.length ) {
137 - this.emit( 'beforePop' );
138 - var childModel = this.children[this.children.length - 1];
139 - childModel.detach();
140 - childModel.removeListener( 'update', this.emitUpdate );
141 - this.children.pop();
142 - this.adjustContentLength( -childModel.getElementLength(), true );
143 - this.emit( 'afterPop' );
144 - this.emit( 'update' );
145 - return childModel;
146 - }
147 -};
148 -
149 -/**
150 - * Removes a node from the beginning of this node's children
151 - *
152 - * @method
153 - * @returns {es.DocumentModelNode} Removed childModel
154 - * @emits beforeShift
155 - * @emits afterShift
156 - * @emits update
157 - */
158 -es.DocumentModelNode.prototype.shift = function() {
159 - if ( this.children.length ) {
160 - this.emit( 'beforeShift' );
161 - var childModel = this.children[0];
162 - childModel.detach();
163 - childModel.removeListener( 'update', this.emitUpdate );
164 - this.children.shift();
165 - this.adjustContentLength( -childModel.getElementLength(), true );
166 - this.emit( 'afterShift' );
167 - this.emit( 'update' );
168 - return childModel;
169 - }
170 -};
171 -
172 -/**
173 - * Adds and removes nodes from this node's children.
174 - *
175 - * @method
176 - * @param {Integer} index Index to remove and or insert nodes at
177 - * @param {Integer} howmany Number of nodes to remove
178 - * @param {es.DocumentModelNode} [...] Variadic list of nodes to insert
179 - * @returns {es.DocumentModelNode[]} Removed nodes
180 - * @emits beforeSplice (index, howmany, [...])
181 - * @emits afterSplice (index, howmany, [...])
182 - * @emits update
183 - */
184 -es.DocumentModelNode.prototype.splice = function( index, howmany ) {
185 - var i,
186 - length,
187 - args = Array.prototype.slice.call( arguments, 0 ),
188 - diff = 0;
189 - this.emit.apply( this, ['beforeSplice'].concat( args ) );
190 - if ( args.length >= 3 ) {
191 - for ( i = 2, length = args.length; i < length; i++ ) {
192 - diff += args[i].getElementLength();
193 - args[i].attach( this );
194 - }
195 - }
196 - var removed = this.children.splice.apply( this.children, args );
197 - for ( i = 0, length = removed.length; i < length; i++ ) {
198 - diff -= removed[i].getElementLength();
199 - removed[i].detach();
200 - removed[i].removeListener( 'update', this.emitUpdate );
201 - }
202 - this.adjustContentLength( diff, true );
203 - this.emit.apply( this, ['afterSplice'].concat( args ) );
204 - this.emit( 'update' );
205 - return removed;
206 -};
207 -
208 -/**
209 - * Sorts this node's children.
210 - *
211 - * @method
212 - * @param {Function} sortfunc Function to use when sorting
213 - * @emits beforeSort (sortfunc)
214 - * @emits afterSort (sortfunc)
215 - * @emits update
216 - */
217 -es.DocumentModelNode.prototype.sort = function( sortfunc ) {
218 - this.emit( 'beforeSort', sortfunc );
219 - this.children.sort( sortfunc );
220 - this.emit( 'afterSort', sortfunc );
221 - this.emit( 'update' );
222 -};
223 -
224 -/**
225 - * Reverses the order of this node's children.
226 - *
227 - * @method
228 - * @emits beforeReverse
229 - * @emits afterReverse
230 - * @emits update
231 - */
232 -es.DocumentModelNode.prototype.reverse = function() {
233 - this.emit( 'beforeReverse' );
234 - this.children.reverse();
235 - this.emit( 'afterReverse' );
236 - this.emit( 'update' );
237 -};
238 -
239 -/**
24059 * Gets a reference to this node's parent.
24160 *
24261 * @method
@@ -258,26 +77,24 @@
25978 /**
26079 * Sets the root node to this and all of it's children.
26180 *
 81+ * This method is overridden by nodes with children.
 82+ *
26283 * @method
26384 * @param {es.DocumentModelNode} root Node to use as root
26485 */
26586 es.DocumentModelNode.prototype.setRoot = function( root ) {
26687 this.root = root;
267 - for ( var i = 0; i < this.children.length; i++ ) {
268 - this.children[i].setRoot( root );
269 - }
27088 };
27189
27290 /**
27391 * Clears the root node from this and all of it's children.
27492 *
 93+ * This method is overridden by nodes with children.
 94+ *
27595 * @method
27696 */
27797 es.DocumentModelNode.prototype.clearRoot = function() {
27898 this.root = null;
279 - for ( var i = 0; i < this.children.length; i++ ) {
280 - this.children[i].clearRoot();
281 - }
28299 };
283100
284101 /**
@@ -402,48 +219,6 @@
403220 return null;
404221 };
405222
406 -/**
407 - * Gets the content length.
408 - *
409 - * FIXME: This method makes assumptions that a node with a data property is a DocumentModel, which
410 - * may be an issue if sub-classes of DocumentModelNode other than DocumentModel have a data property
411 - * as well. A safer way of determining this would be helpful in preventing future bugs.
412 - *
413 - * @method
414 - * @param {es.Range} [range] Range of content to get
415 - * @returns {Integer} Length of content
416 - */
417 -es.DocumentModelNode.prototype.getContent = function( range ) {
418 - // Find root
419 - var root = this.data ? this : ( this.root.data ? this.root : null );
420 -
421 - if ( root ) {
422 - return root.getContentFromNode( this, range );
423 - }
424 - return [];
425 -};
426 -
427 -/**
428 - * Gets plain text version of the content within a specific range.
429 - *
430 - * @method
431 - * @param {es.Range} [range] Range of text to get
432 - * @returns {String} Text within given range
433 - */
434 -es.DocumentModelNode.prototype.getText = function( range ) {
435 - var content = this.getContent( range );
436 - // Copy characters
437 - var text = '';
438 - for ( var i = 0, length = content.length; i < length; i++ ) {
439 - // If not using in IE6 or IE7 (which do not support array access for strings) use this..
440 - // text += this.data[i][0];
441 - // Otherwise use this...
442 - text += typeof content[i] === 'string' ? content[i] : content[i][0];
443 - }
444 - return text;
445 -};
446 -
447223 /* Inheritance */
448224
449 -es.extendClass( es.DocumentModelNode, es.DocumentNode );
450225 es.extendClass( es.DocumentModelNode, es.EventEmitter );
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewLeafNode.js
@@ -2,6 +2,7 @@
33 * Creates an es.DocumentViewLeafNode object.
44 *
55 * @class
 6+ * @abstract
67 * @constructor
78 * @extends {es.DocumentViewNode}
89 * @param model {es.ModelNode} Model to observe
Index: trunk/extensions/VisualEditor/modules/es/bases/es.EventEmitter.js
@@ -2,6 +2,7 @@
33 * Event emitter.
44 *
55 * @class
 6+ * @abstract
67 * @constructor
78 * @property events {Object}
89 */
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentModelLeafNode.js
@@ -0,0 +1,82 @@
 2+/**
 3+ * Creates an es.DocumentModelLeafNode object.
 4+ *
 5+ * @class
 6+ * @abstract
 7+ * @constructor
 8+ * @extends {es.DocumentModelNode}
 9+ * @extends {es.DocumentNode}
 10+ * @param {String} type Symbolic name of node type
 11+ * @param {Integer} length Length of content data in document
 12+ */
 13+es.DocumentModelLeafNode = function( type, element, length ) {
 14+ // Inheritance
 15+ es.DocumentModelNode.call( this, type, element, length );
 16+
 17+ // Properties
 18+ this.contentLength = length;
 19+};
 20+
 21+/* Methods */
 22+
 23+/**
 24+ * Gets a plain object representation of the document's data.
 25+ *
 26+ * The resulting object is compatible with es.DocumentModel.newFromPlainObject.
 27+ *
 28+ * @method
 29+ * @returns {Object} Plain object representation
 30+ */
 31+es.DocumentModelLeafNode.prototype.getPlainObject = function() {
 32+ var obj = { 'type': this.type };
 33+ if ( this.element && this.element.attributes ) {
 34+ obj.attributes = es.copyObject( this.element.attributes );
 35+ }
 36+ obj.content = es.DocumentModel.getExpandedContentData( this.getContent() );
 37+ return obj;
 38+};
 39+
 40+/**
 41+ * Gets the content length.
 42+ *
 43+ * FIXME: This method makes assumptions that a node with a data property is a DocumentModel, which
 44+ * may be an issue if sub-classes of DocumentModelLeafNode other than DocumentModel have a data property
 45+ * as well. A safer way of determining this would be helpful in preventing future bugs.
 46+ *
 47+ * @method
 48+ * @param {es.Range} [range] Range of content to get
 49+ * @returns {Integer} Length of content
 50+ */
 51+es.DocumentModelLeafNode.prototype.getContent = function( range ) {
 52+ // Find root
 53+ var root = this.data ? this : ( this.root.data ? this.root : null );
 54+
 55+ if ( root ) {
 56+ return root.getContentFromNode( this, range );
 57+ }
 58+ return [];
 59+};
 60+
 61+/**
 62+ * Gets plain text version of the content within a specific range.
 63+ *
 64+ * @method
 65+ * @param {es.Range} [range] Range of text to get
 66+ * @returns {String} Text within given range
 67+ */
 68+es.DocumentModelLeafNode.prototype.getText = function( range ) {
 69+ var content = this.getContent( range );
 70+ // Copy characters
 71+ var text = '';
 72+ for ( var i = 0, length = content.length; i < length; i++ ) {
 73+ // If not using in IE6 or IE7 (which do not support array access for strings) use this..
 74+ // text += this.data[i][0];
 75+ // Otherwise use this...
 76+ text += typeof content[i] === 'string' ? content[i] : content[i][0];
 77+ }
 78+ return text;
 79+};
 80+
 81+/* Inheritance */
 82+
 83+es.extendClass( es.DocumentModelLeafNode, es.DocumentModelNode );
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewBranchNode.js
@@ -2,21 +2,144 @@
33 * Creates an es.DocumentViewBranchNode object.
44 *
55 * @class
 6+ * @abstract
67 * @constructor
78 * @extends {es.DocumentViewNode}
 9+ * @extends {es.DocumentBranchNode}
810 * @param model {es.ModelNode} Model to observe
911 * @param {jQuery} [$element] Element to use as a container
1012 */
1113 es.DocumentViewBranchNode = function( model, $element, horizontal ) {
1214 // Inheritance
1315 es.DocumentViewNode.call( this, model, $element );
 16+ es.DocumentBranchNode.call( this );
1417
1518 // Properties
1619 this.horizontal = horizontal || false;
 20+
 21+ if ( model ) {
 22+ // Append existing model children
 23+ var childModels = model.getChildren();
 24+ for ( var i = 0; i < childModels.length; i++ ) {
 25+ this.onAfterPush( childModels[i] );
 26+ }
 27+
 28+ // Observe and mimic changes on model
 29+ this.addListenerMethods( this, {
 30+ 'afterPush': 'onAfterPush',
 31+ 'afterUnshift': 'onAfterUnshift',
 32+ 'afterPop': 'onAfterPop',
 33+ 'afterShift': 'onAfterShift',
 34+ 'afterSplice': 'onAfterSplice',
 35+ 'afterSort': 'onAfterSort',
 36+ 'afterReverse': 'onAfterReverse'
 37+ } );
 38+ }
1739 };
1840
1941 /* Methods */
2042
 43+es.DocumentViewBranchNode.prototype.onAfterPush = function( childModel ) {
 44+ var childView = childModel.createView();
 45+ this.emit( 'beforePush', childView );
 46+ childView.attach( this );
 47+ childView.on( 'update', this.emitUpdate );
 48+ // Update children
 49+ this.children.push( childView );
 50+ // Update DOM
 51+ this.$.append( childView.$ );
 52+ this.emit( 'afterPush', childView );
 53+ this.emit( 'update' );
 54+};
 55+
 56+es.DocumentViewBranchNode.prototype.onAfterUnshift = function( childModel ) {
 57+ var childView = childModel.createView();
 58+ this.emit( 'beforeUnshift', childView );
 59+ childView.attach( this );
 60+ childView.on( 'update', this.emitUpdate );
 61+ // Update children
 62+ this.children.unshift( childView );
 63+ // Update DOM
 64+ this.$.prepend( childView.$ );
 65+ this.emit( 'afterUnshift', childView );
 66+ this.emit( 'update' );
 67+};
 68+
 69+es.DocumentViewBranchNode.prototype.onAfterPop = function() {
 70+ this.emit( 'beforePop' );
 71+ // Update children
 72+ var childView = this.children.pop();
 73+ childView.detach();
 74+ childView.removeEventListener( 'update', this.emitUpdate );
 75+ // Update DOM
 76+ childView.$.detach();
 77+ this.emit( 'afterPop' );
 78+ this.emit( 'update' );
 79+};
 80+
 81+es.DocumentViewBranchNode.prototype.onAfterShift = function() {
 82+ this.emit( 'beforeShift' );
 83+ // Update children
 84+ var childView = this.children.shift();
 85+ childView.detach();
 86+ childView.removeEventListener( 'update', this.emitUpdate );
 87+ // Update DOM
 88+ childView.$.detach();
 89+ this.emit( 'afterShift' );
 90+ this.emit( 'update' );
 91+};
 92+
 93+es.DocumentViewBranchNode.prototype.onAfterSplice = function( index, howmany ) {
 94+ var args = Array.prototype.slice( arguments, 0 );
 95+ this.emit.apply( ['beforeSplice'].concat( args ) );
 96+ // Update children
 97+ this.splice.apply( this, args );
 98+ // Update DOM
 99+ this.$.children()
 100+ // Removals
 101+ .slice( index, index + howmany )
 102+ .detach()
 103+ .end()
 104+ // Insertions
 105+ .get( index )
 106+ .after( $.map( args.slice( 2 ), function( childView ) {
 107+ return childView.$;
 108+ } ) );
 109+ this.emit.apply( ['afterSplice'].concat( args ) );
 110+ this.emit( 'update' );
 111+};
 112+
 113+es.DocumentViewBranchNode.prototype.onAfterSort = function() {
 114+ this.emit( 'beforeSort' );
 115+ var childModels = this.model.getChildren();
 116+ for ( var i = 0; i < childModels.length; i++ ) {
 117+ for ( var j = 0; j < this.children.length; j++ ) {
 118+ if ( this.children[j].getModel() === childModels[i] ) {
 119+ var childView = this.children[j];
 120+ // Update children
 121+ this.children.splice( j, 1 );
 122+ this.children.push( childView );
 123+ // Update DOM
 124+ this.$.append( childView.$ );
 125+ }
 126+ }
 127+ }
 128+ this.emit( 'afterSort' );
 129+ this.emit( 'update' );
 130+};
 131+
 132+es.DocumentViewBranchNode.prototype.onAfterReverse = function() {
 133+ this.emit( 'beforeReverse' );
 134+ // Update children
 135+ this.reverse();
 136+ // Update DOM
 137+ this.$.children().each( function() {
 138+ $(this).prependTo( $(this).parent() );
 139+ } );
 140+ this.emit( 'afterReverse' );
 141+ this.emit( 'update' );
 142+};
 143+
21144 /**
22145 * Render content.
23146 *
@@ -116,3 +239,4 @@
117240 /* Inheritance */
118241
119242 es.extendClass( es.DocumentViewBranchNode, es.DocumentViewNode );
 243+es.extendClass( es.DocumentViewBranchNode, es.DocumentBranchNode );

Status & tagging log