Index: trunk/extensions/VisualEditor/tests/es.DocumentNode.test.js |
— | — | @@ -1,9 +1,12 @@ |
2 | 2 | module( 'es/bases' ); |
3 | 3 | |
4 | 4 | function DocumentNodeStub( items, name, size ) { |
| 5 | + // Inheritance |
| 6 | + es.DocumentBranchNode.call( this, items ); |
| 7 | + |
| 8 | + // Properties |
5 | 9 | this.name = name; |
6 | 10 | this.size = size; |
7 | | - return es.extendObject( new es.DocumentNode( items ), this ); |
8 | 11 | } |
9 | 12 | |
10 | 13 | DocumentNodeStub.prototype.getContentLength = function() { |
— | — | @@ -15,6 +18,8 @@ |
16 | 19 | return this.size + 2; |
17 | 20 | }; |
18 | 21 | |
| 22 | +es.extendClass( DocumentNodeStub, es.DocumentBranchNode ); |
| 23 | + |
19 | 24 | test( 'es.DocumentNode', function() { |
20 | 25 | |
21 | 26 | // Stub test |
Index: trunk/extensions/VisualEditor/tests/index.html |
— | — | @@ -12,14 +12,22 @@ |
13 | 13 | <h2 id="qunit-userAgent"></h2> |
14 | 14 | <ol id="qunit-tests"></ol> |
15 | 15 | <div id="qunit-fixture">test markup</div> |
| 16 | + |
| 17 | + <!-- EditSurface --> |
16 | 18 | <script src="../modules/jquery/jquery.js"></script> |
17 | 19 | <script src="../modules/qunit/qunit.js"></script> |
18 | 20 | <script src="../modules/es/es.js"></script> |
19 | 21 | <script src="../modules/es/es.Range.js"></script> |
20 | 22 | <script src="../modules/es/es.Transaction.js"></script> |
| 23 | + |
| 24 | + <!-- Bases --> |
21 | 25 | <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> |
23 | 27 | <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 --> |
24 | 32 | <script src="../modules/es/models/es.DocumentModel.js"></script> |
25 | 33 | <script src="../modules/es/models/es.ListItemModel.js"></script> |
26 | 34 | <script src="../modules/es/models/es.ListModel.js"></script> |
— | — | @@ -27,6 +35,8 @@ |
28 | 36 | <script src="../modules/es/models/es.TableCellModel.js"></script> |
29 | 37 | <script src="../modules/es/models/es.TableModel.js"></script> |
30 | 38 | <script src="../modules/es/models/es.TableRowModel.js"></script> |
| 39 | + |
| 40 | + <!-- Tests --> |
31 | 41 | <script src="es.test.js"></script> |
32 | 42 | <script src="es.DocumentNode.test.js"></script> |
33 | 43 | <script src="es.DocumentModel.test.js"></script> |
Index: trunk/extensions/VisualEditor/tests/es.DocumentModel.test.js |
— | — | @@ -224,9 +224,14 @@ |
225 | 225 | console.log( 'mismatched content lengths', a[i], b[i] ); |
226 | 226 | return false; |
227 | 227 | } |
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 ) { |
229 | 231 | return false; |
230 | 232 | } |
| 233 | + if ( aIsBranch && !equalLengths( a[i].getChildren(), b[i].getChildren() ) ) { |
| 234 | + return false; |
| 235 | + } |
231 | 236 | } |
232 | 237 | return true; |
233 | 238 | } |
— | — | @@ -891,13 +896,17 @@ |
892 | 897 | |
893 | 898 | test( 'es.DocumentDocumentModelNode child operations', 20, function() { |
894 | 899 | // 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( |
899 | 908 | '4', |
900 | 909 | null, |
901 | | - [new es.DocumentModelNode( '4a' ), new es.DocumentModelNode( '4b' )] |
| 910 | + [new es.DocumentModelBranchNode( '4a' ), new es.DocumentModelBranchNode( '4b' )] |
902 | 911 | ); |
903 | 912 | |
904 | 913 | // Event triggering is detected using a callback that increments a counter |
Index: trunk/extensions/VisualEditor/demo/index.html |
— | — | @@ -68,8 +68,10 @@ |
69 | 69 | |
70 | 70 | <!-- Bases --> |
71 | 71 | <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> |
73 | 73 | <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> |
74 | 76 | <script src="../modules/es/bases/es.DocumentViewNode.js"></script> |
75 | 77 | <script src="../modules/es/bases/es.DocumentViewBranchNode.js"></script> |
76 | 78 | <script src="../modules/es/bases/es.DocumentViewLeafNode.js"></script> |
Index: trunk/extensions/VisualEditor/modules/es/models/es.DocumentModel.js |
— | — | @@ -6,17 +6,18 @@ |
7 | 7 | * |
8 | 8 | * @class |
9 | 9 | * @constructor |
10 | | - * @extends {es.DocumentModelNode} |
| 10 | + * @extends {es.DocumentModelBranchNode} |
11 | 11 | * @param {Array} data Model data to initialize with, such as data from es.DocumentModel.getData() |
12 | 12 | * @param {Object} attributes Document attributes |
13 | 13 | */ |
14 | 14 | es.DocumentModel = function( data, attributes ) { |
15 | 15 | // Inheritance |
16 | | - es.DocumentModelNode.call( this, 'document', null, data ? data.length : 0 ); |
17 | | - |
| 16 | + es.DocumentModelBranchNode.call( this, 'document', null ); |
| 17 | + |
18 | 18 | // Properties |
19 | 19 | this.data = es.isArray( data ) ? data : []; |
20 | 20 | this.attributes = es.isPlainObject( attributes ) ? attributes : {}; |
| 21 | + this.contentLength = this.data.length; |
21 | 22 | |
22 | 23 | // Auto-generate model tree |
23 | 24 | var nodes = es.DocumentModel.createNodesFromData( this.data ); |
— | — | @@ -273,7 +274,7 @@ |
274 | 275 | * it's child nodes. |
275 | 276 | */ |
276 | 277 | es.DocumentModel.createNodesFromData = function( data ) { |
277 | | - var currentNode = new es.DocumentModelNode(); |
| 278 | + var currentNode = new es.DocumentModelBranchNode(); |
278 | 279 | for ( var i = 0, length = data.length; i < length; i++ ) { |
279 | 280 | if ( data[i].type !== undefined ) { |
280 | 281 | // It's an element, figure out it's type |
— | — | @@ -290,7 +291,7 @@ |
291 | 292 | throw 'Unsuported element error. No class registered for element type: ' + type; |
292 | 293 | } |
293 | 294 | // 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 ); |
295 | 296 | // Add the new model node as a child |
296 | 297 | currentNode.push( newNode ); |
297 | 298 | // Descend into the new model node |
— | — | @@ -1251,4 +1252,4 @@ |
1252 | 1253 | |
1253 | 1254 | /* Inheritance */ |
1254 | 1255 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelLeafNode} |
8 | 8 | * @param {Object} element Document data element of this node |
9 | 9 | * @param {Integer} length Length of document data element |
10 | 10 | */ |
11 | 11 | es.HeadingModel = function( element, length ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'heading', element, length ); |
| 13 | + es.DocumentModelLeafNode.call( this, 'heading', element, length ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelBranchNode} |
8 | 8 | * @param {Object} element Document data element of this node |
9 | 9 | * @param {es.DocumentModelNode[]} contents List of child nodes to initially add |
10 | 10 | */ |
11 | 11 | es.TableRowModel = function( element, contents ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'tableRow', element, contents ); |
| 13 | + es.DocumentModelBranchNode.call( this, 'tableRow', element, contents ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelLeafNode} |
8 | 8 | * @param {Object} element Document data element of this node |
9 | 9 | * @param {Integer} length Length of document data element |
10 | 10 | */ |
11 | 11 | es.ParagraphModel = function( element, length ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'paragraph', element, length ); |
| 13 | + es.DocumentModelLeafNode.call( this, 'paragraph', element, length ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelBranchNode} |
8 | 8 | * @param {Object} element Document data element of this node |
9 | 9 | * @param {es.DocumentModelNode[]} contents List of child nodes to initially add |
10 | 10 | */ |
11 | 11 | es.TableCellModel = function( element, contents ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'tableCell', element, contents ); |
| 13 | + es.DocumentModelBranchNode.call( this, 'tableCell', element, contents ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelBranchNode} |
8 | 8 | * @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 |
10 | 10 | */ |
11 | 11 | es.TableModel = function( element, contents ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'table', element, contents ); |
| 13 | + es.DocumentModelBranchNode.call( this, 'table', element, contents ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelLeafNode} |
8 | 8 | * @param {Object} element Document data element of this node |
9 | 9 | * @param {Integer} length Length of document data element |
10 | 10 | */ |
11 | 11 | es.ListItemModel = function( element, length ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'listItem', element, length ); |
| 13 | + es.DocumentModelLeafNode.call( this, 'listItem', element, length ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @extends {es.DocumentModelNode} |
| 7 | + * @extends {es.DocumentModelBranchNode} |
8 | 8 | * @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 |
10 | 10 | */ |
11 | 11 | es.ListModel = function( element, contents ) { |
12 | 12 | // Inheritance |
13 | | - es.DocumentModelNode.call( this, 'list', element, contents ); |
| 13 | + es.DocumentModelBranchNode.call( this, 'list', element, contents ); |
14 | 14 | }; |
15 | 15 | |
16 | 16 | /* Methods */ |
— | — | @@ -35,4 +35,4 @@ |
36 | 36 | |
37 | 37 | /* Inheritance */ |
38 | 38 | |
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 @@ |
40 | 40 | }; |
41 | 41 | |
42 | 42 | 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 |
54 | 50 | } |
| 51 | + return []; |
55 | 52 | }; |
56 | 53 | |
57 | 54 | /* Methods */ |
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewNode.js |
— | — | @@ -9,8 +9,8 @@ |
10 | 10 | * Child objects must extend es.DocumentViewNode. |
11 | 11 | * |
12 | 12 | * @class |
| 13 | + * @abstract |
13 | 14 | * @constructor |
14 | | - * @extends {es.DocumentNode} |
15 | 15 | * @extends {es.EventEmitter} |
16 | 16 | * @param model {es.ModelNode} Model to observe |
17 | 17 | * @param {jQuery} [$element=New DIV element] Element to use as a container |
— | — | @@ -19,12 +19,10 @@ |
20 | 20 | */ |
21 | 21 | es.DocumentViewNode = function( model, $element ) { |
22 | 22 | // Inheritance |
23 | | - es.DocumentNode.call( this ); |
24 | 23 | es.EventEmitter.call( this ); |
25 | 24 | |
26 | 25 | // Properties |
27 | 26 | this.model = model; |
28 | | - this.children = []; |
29 | 27 | this.$ = $element || $( '<div/>' ); |
30 | 28 | |
31 | 29 | // Reusable function for passing update events upstream |
— | — | @@ -32,128 +30,8 @@ |
33 | 31 | this.emitUpdate = function() { |
34 | 32 | _this.emit( 'update' ); |
35 | 33 | }; |
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 | | - } |
55 | 34 | }; |
56 | 35 | |
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 | | - |
158 | 36 | /** |
159 | 37 | * Gets a reference to the model this node observes. |
160 | 38 | * |
— | — | @@ -220,5 +98,4 @@ |
221 | 99 | |
222 | 100 | /* Inheritance */ |
223 | 101 | |
224 | | -es.extendClass( es.DocumentViewNode, es.DocumentNode ); |
225 | 102 | 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 @@ |
2 | 2 | /** |
3 | 3 | * Creates an es.DocumentModelNode object. |
4 | 4 | * |
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 | | - * |
8 | 5 | * @class |
9 | 6 | * @abstract |
10 | 7 | * @constructor |
11 | | - * @extends {es.DocumentNode} |
12 | 8 | * @extends {es.EventEmitter} |
| 9 | + * @param {String} type Symbolic name of node type |
13 | 10 | * @param {Integer|Array} contents Either Length of content or array of child nodes to append |
14 | 11 | * @property {Integer} contentLength Length of content |
15 | 12 | */ |
16 | | -es.DocumentModelNode = function( type, element, contents ) { |
| 13 | +es.DocumentModelNode = function( type, element, length ) { |
17 | 14 | // Inheritance |
18 | | - es.DocumentNode.call( this ); |
19 | 15 | es.EventEmitter.call( this ); |
20 | 16 | |
21 | 17 | // Reusable function for passing update events upstream |
— | — | @@ -28,17 +24,7 @@ |
29 | 25 | this.parent = null; |
30 | 26 | this.root = this; |
31 | 27 | 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; |
43 | 29 | }; |
44 | 30 | |
45 | 31 | /* Abstract Methods */ |
— | — | @@ -54,8 +40,6 @@ |
55 | 41 | throw 'DocumentModelNode.createView not implemented in this subclass:' + this.constructor; |
56 | 42 | }; |
57 | 43 | |
58 | | -/* Methods */ |
59 | | - |
60 | 44 | /** |
61 | 45 | * Gets a plain object representation of the document's data. |
62 | 46 | * |
— | — | @@ -65,177 +49,12 @@ |
66 | 50 | * @returns {Object} Plain object representation |
67 | 51 | */ |
68 | 52 | 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; |
82 | 54 | }; |
83 | 55 | |
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 */ |
104 | 57 | |
105 | 58 | /** |
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 | | -/** |
240 | 59 | * Gets a reference to this node's parent. |
241 | 60 | * |
242 | 61 | * @method |
— | — | @@ -258,26 +77,24 @@ |
259 | 78 | /** |
260 | 79 | * Sets the root node to this and all of it's children. |
261 | 80 | * |
| 81 | + * This method is overridden by nodes with children. |
| 82 | + * |
262 | 83 | * @method |
263 | 84 | * @param {es.DocumentModelNode} root Node to use as root |
264 | 85 | */ |
265 | 86 | es.DocumentModelNode.prototype.setRoot = function( root ) { |
266 | 87 | this.root = root; |
267 | | - for ( var i = 0; i < this.children.length; i++ ) { |
268 | | - this.children[i].setRoot( root ); |
269 | | - } |
270 | 88 | }; |
271 | 89 | |
272 | 90 | /** |
273 | 91 | * Clears the root node from this and all of it's children. |
274 | 92 | * |
| 93 | + * This method is overridden by nodes with children. |
| 94 | + * |
275 | 95 | * @method |
276 | 96 | */ |
277 | 97 | es.DocumentModelNode.prototype.clearRoot = function() { |
278 | 98 | this.root = null; |
279 | | - for ( var i = 0; i < this.children.length; i++ ) { |
280 | | - this.children[i].clearRoot(); |
281 | | - } |
282 | 99 | }; |
283 | 100 | |
284 | 101 | /** |
— | — | @@ -402,48 +219,6 @@ |
403 | 220 | return null; |
404 | 221 | }; |
405 | 222 | |
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 | | - |
447 | 223 | /* Inheritance */ |
448 | 224 | |
449 | | -es.extendClass( es.DocumentModelNode, es.DocumentNode ); |
450 | 225 | es.extendClass( es.DocumentModelNode, es.EventEmitter ); |
Index: trunk/extensions/VisualEditor/modules/es/bases/es.DocumentViewLeafNode.js |
— | — | @@ -2,6 +2,7 @@ |
3 | 3 | * Creates an es.DocumentViewLeafNode object. |
4 | 4 | * |
5 | 5 | * @class |
| 6 | + * @abstract |
6 | 7 | * @constructor |
7 | 8 | * @extends {es.DocumentViewNode} |
8 | 9 | * @param model {es.ModelNode} Model to observe |
Index: trunk/extensions/VisualEditor/modules/es/bases/es.EventEmitter.js |
— | — | @@ -2,6 +2,7 @@ |
3 | 3 | * Event emitter. |
4 | 4 | * |
5 | 5 | * @class |
| 6 | + * @abstract |
6 | 7 | * @constructor |
7 | 8 | * @property events {Object} |
8 | 9 | */ |
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 @@ |
3 | 3 | * Creates an es.DocumentViewBranchNode object. |
4 | 4 | * |
5 | 5 | * @class |
| 6 | + * @abstract |
6 | 7 | * @constructor |
7 | 8 | * @extends {es.DocumentViewNode} |
| 9 | + * @extends {es.DocumentBranchNode} |
8 | 10 | * @param model {es.ModelNode} Model to observe |
9 | 11 | * @param {jQuery} [$element] Element to use as a container |
10 | 12 | */ |
11 | 13 | es.DocumentViewBranchNode = function( model, $element, horizontal ) { |
12 | 14 | // Inheritance |
13 | 15 | es.DocumentViewNode.call( this, model, $element ); |
| 16 | + es.DocumentBranchNode.call( this ); |
14 | 17 | |
15 | 18 | // Properties |
16 | 19 | 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 | + } |
17 | 39 | }; |
18 | 40 | |
19 | 41 | /* Methods */ |
20 | 42 | |
| 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 | + |
21 | 144 | /** |
22 | 145 | * Render content. |
23 | 146 | * |
— | — | @@ -116,3 +239,4 @@ |
117 | 240 | /* Inheritance */ |
118 | 241 | |
119 | 242 | es.extendClass( es.DocumentViewBranchNode, es.DocumentViewNode ); |
| 243 | +es.extendClass( es.DocumentViewBranchNode, es.DocumentBranchNode ); |