Split Broke DefinitionListNode out of ListNode and DefinitionListItemNode out of ListItemNode

* Makes it simpler in the linear model because we don't have to use style: "item" for regular list items and style: "definition" for definition lists
* Enforces correct nesting through existing node rules systems
* Updates tests accordingly

Change-Id: I64d80af938e325f1961226505bdc386bb35ccdda
This commit is contained in:
Trevor Parscal 2012-05-04 11:56:32 -07:00
parent 11a3b6886b
commit 9887dbd96f
11 changed files with 293 additions and 75 deletions

View file

@ -115,6 +115,8 @@ include( '../../modules/sandbox/base.php' );
<script src="../../modules/ve2/dm/ve.dm.Document.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.AlienNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DocumentNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.HeadingNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.ImageNode.js"></script>
@ -142,6 +144,8 @@ include( '../../modules/sandbox/base.php' );
<script src="../../modules/ve2/ce/ve.ce.Surface.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.AlienNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DefinitionListItemNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DefinitionListNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DocumentNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.HeadingNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.ImageNode.js"></script>

View file

@ -0,0 +1,91 @@
/**
* ContentEditable node for a definition list item.
*
* @class
* @constructor
* @extends {ve.ce.BranchNode}
* @param model {ve.dm.DefinitionListItemNode} Model to observe
*/
ve.ce.DefinitionListItemNode = function( model ) {
// Inheritance
ve.ce.BranchNode.call( this, model, ve.ce.DefinitionListItemNode.getDomWrapper( model ) );
// Properties
this.currentStyle = model.getAttribute( 'style' );
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );
};
/* Static Members */
/**
* Node rules.
*
* @see ve.ce.NodeFactory
* @static
* @member
*/
ve.ce.DefinitionListItemNode.rules = {
'canHaveChildren': true,
'canHaveGrandchildren': true,
'canBeSplit': false
};
/**
* Mapping of list item style values and DOM wrapper element types.
*
* @static
* @member
*/
ve.ce.DefinitionListItemNode.domWrapperElementTypes = {
'definition': 'dd',
'term': 'dt'
};
/* Static Methods */
/**
* Gets an appropriate DOM wrapper for the model.
*
* This method is static because it is used before the node is fully constructed. Before all parent
* constructors are called this.model may not be ready to be used.
*
* @static
* @method
* @param {ve.dm.DefinitionListItemNode} model Model to create DOM wrapper for
* @returns {jQuery} Selection containing DOM wrapper
*/
ve.ce.DefinitionListItemNode.getDomWrapper = function( model ) {
var style = model.getAttribute( 'style' ),
type = ve.ce.DefinitionListItemNode.domWrapperElementTypes[style];
if ( type === undefined ) {
throw 'Invalid style attribute in list item node model: ' + style;
}
return $( '<' + type + '></' + type + '>' );
};
/* Methods */
/**
* Responds to model update events.
*
* If the style changed since last update the DOM wrapper will be replaced with an appropriate one.
*
* @method
*/
ve.ce.DefinitionListItemNode.prototype.onUpdate = function() {
var style = this.model.getAttribute( 'style' );
if ( style !== this.currentStyle ) {
this.currentStyle = style;
this.replaceDomWrapper( ve.ce.DefinitionListItemNode.getDomWrapper( this.model ) );
}
};
/* Registration */
ve.ce.factory.register( 'definitionListItem', ve.ce.DefinitionListItemNode );
/* Inheritance */
ve.extendClass( ve.ce.DefinitionListItemNode, ve.ce.BranchNode );

View file

@ -0,0 +1,35 @@
/**
* ContentEditable node for a definition list.
*
* @class
* @constructor
* @extends {ve.ce.BranchNode}
* @param model {ve.dm.DefinitionListNode} Model to observe
*/
ve.ce.DefinitionListNode = function( model ) {
// Inheritance
ve.ce.BranchNode.call( this, model, $( '<dl></dl>' ) );
};
/* Static Members */
/**
* Node rules.
*
* @see ve.ce.NodeFactory
* @static
* @member
*/
ve.ce.DefinitionListNode.rules = {
'canHaveChildren': true,
'canHaveGrandchildren': true,
'canBeSplit': false
};
/* Registration */
ve.ce.factory.register( 'definitionList', ve.ce.DefinitionListNode );
/* Inheritance */
ve.extendClass( ve.ce.DefinitionListNode, ve.ce.BranchNode );

View file

@ -8,13 +8,7 @@
*/
ve.ce.ListItemNode = function( model ) {
// Inheritance
ve.ce.BranchNode.call( this, model, ve.ce.ListItemNode.getDomWrapper( model ) );
// Properties
this.currentStyle = model.getAttribute( 'style' );
// Events
this.model.addListenerMethod( this, 'update', 'onUpdate' );
ve.ce.BranchNode.call( this, model, $( '<li></li>' ) );
};
/* Static Members */
@ -32,57 +26,6 @@ ve.ce.ListItemNode.rules = {
'canBeSplit': false
};
/**
* Mapping of list item style values and DOM wrapper element types.
*
* @static
* @member
*/
ve.ce.ListItemNode.domWrapperElementTypes = {
'item': 'li',
'definition': 'dd',
'term': 'dt'
};
/* Static Methods */
/**
* Gets an appropriate DOM wrapper for the model.
*
* This method is static because it is used before the node is fully constructed. Before all parent
* constructors are called this.model may not be ready to be used.
*
* @static
* @method
* @param {ve.dm.ListItemNode} model Model to create DOM wrapper for
* @returns {jQuery} Selection containing DOM wrapper
*/
ve.ce.ListItemNode.getDomWrapper = function( model ) {
var style = model.getAttribute( 'style' ),
type = ve.ce.ListItemNode.domWrapperElementTypes[style];
if ( type === undefined ) {
throw 'Invalid style attribute in list item node model: ' + style;
}
return $( '<' + type + '></' + type + '>' );
};
/* Methods */
/**
* Responds to model update events.
*
* If the style changed since last update the DOM wrapper will be replaced with an appropriate one.
*
* @method
*/
ve.ce.ListItemNode.prototype.onUpdate = function() {
var style = this.model.getAttribute( 'style' );
if ( style !== this.currentStyle ) {
this.currentStyle = style;
this.replaceDomWrapper( ve.ce.ListItemNode.getDomWrapper( this.model ) );
}
};
/* Registration */
ve.ce.factory.register( 'listItem', ve.ce.ListItemNode );

View file

@ -40,8 +40,7 @@ ve.ce.ListNode.rules = {
*/
ve.ce.ListNode.domWrapperElementTypes = {
'bullet': 'ul',
'number': 'ol',
'definition': 'dl'
'number': 'ol'
};
/* Static Methods */

View file

@ -0,0 +1,37 @@
/**
* DataModel node for a definition list item.
*
* @class
* @constructor
* @extends {ve.dm.BranchNode}
* @param {ve.dm.BranchNode[]} [children] Child nodes to attach
* @param {Object} [attributes] Reference to map of attribute key/value pairs
*/
ve.dm.DefinitionListItemNode = function( children, attributes ) {
// Inheritance
ve.dm.BranchNode.call( this, 'definitionListItem', children, attributes );
};
/* Static Members */
/**
* Node rules.
*
* @see ve.dm.NodeFactory
* @static
* @member
*/
ve.dm.DefinitionListItemNode.rules = {
'canHaveChildren': true,
'canHaveGrandchildren': true,
'childNodeTypes': null,
'parentNodeTypes': ['definitionList']
};
/* Registration */
ve.dm.factory.register( 'definitionListItem', ve.dm.DefinitionListItemNode );
/* Inheritance */
ve.extendClass( ve.dm.DefinitionListItemNode, ve.dm.BranchNode );

View file

@ -0,0 +1,37 @@
/**
* DataModel node for a definition list.
*
* @class
* @constructor
* @extends {ve.dm.BranchNode}
* @param {ve.dm.BranchNode[]} [children] Child nodes to attach
* @param {Object} [attributes] Reference to map of attribute key/value pairs
*/
ve.dm.DefinitionListNode = function( children, attributes ) {
// Inheritance
ve.dm.BranchNode.call( this, 'definitionList', children, attributes );
};
/* Static Members */
/**
* Node rules.
*
* @see ve.dm.NodeFactory
* @static
* @member
*/
ve.dm.DefinitionListNode.rules = {
'canHaveChildren': true,
'canHaveGrandchildren': true,
'childNodeTypes': ['definitionListItem'],
'parentNodeTypes': null
};
/* Registration */
ve.dm.factory.register( 'definitionList', ve.dm.DefinitionListNode );
/* Inheritance */
ve.extendClass( ve.dm.DefinitionListNode, ve.dm.BranchNode );

View file

@ -11,10 +11,10 @@ test( 'getOuterLength', 1, function() {
);
} );
test( 'rebuildNodes', 88, function() {
test( 'rebuildNodes', 114, function() {
var doc = new ve.dm.Document( ve.dm.example.data ),
documentNode = doc.getDocumentNode();
doc.rebuildNodes( documentNode, 1, 1, 5, 30 );
// Test count: ( ( 4 tests x 16 branch nodes ) + ( 3 tests x 8 leaf nodes ) ) = 88
// Test count: ( ( 4 tests x 21 branch nodes ) + ( 3 tests x 10 leaf nodes ) ) = 114
ve.dm.example.nodeTreeEqual( documentNode, ve.dm.example.tree );
} );

View file

@ -2,9 +2,9 @@ module( 've.dm.DocumentFragment' );
/* Tests */
test( 'constructor', 88, function() {
test( 'constructor', 114, function() {
var fragment = new ve.dm.DocumentFragment( ve.dm.example.data );
// Test count: ( ( 4 tests x 16 branch nodes ) + ( 3 tests x 8 leaf nodes ) ) = 88
// Test count: ( ( 4 tests x 21 branch nodes ) + ( 3 tests x 10 leaf nodes ) ) = 114
ve.dm.example.nodeTreeEqual( fragment.getDocumentNode(), ve.dm.example.tree );
} );
@ -13,7 +13,7 @@ test( 'getData', 1, function() {
deepEqual( fragment.getData(), ve.dm.example.data );
} );
test( 'getOffsetMap', 43, function() {
test( 'getOffsetMap', 55, function() {
var fragment = new ve.dm.DocumentFragment( ve.dm.example.data ),
actual = fragment.getOffsetMap(),
expected = ve.dm.example.getOffsetMap( fragment.getDocumentNode() );

View file

@ -31,7 +31,15 @@ ve.dm.example.html =
'</td>' +
'</tr>' +
'</table>' +
'<pre>h<img src="image.png">i</pre>';
'<pre>h<img src="image.png">i</pre>'+
'<dl>' +
'<dt>' +
'<p>j</p>' +
'</dt>' +
'<dd>' +
'<p>k</p>' +
'</dd>' +
'</dl>';
/*
* Linear data.
@ -133,7 +141,31 @@ ve.dm.example.data = [
// 39 - Plain "i"
'i',
// 40 - End of preformatted
{ 'type': '/preformatted' }
{ 'type': '/preformatted' },
// 41 - Beginning of definition list
{ 'type': 'definitionList' },
// 42 - Beginning of definition list term item
{ 'type': 'definitionListItem', 'attributes': { 'style': 'term' } },
// 43 - Beginning of paragraph
{ 'type': 'paragraph' },
// 44 - Plain "j"
'j',
// 45 - End of paragraph
{ 'type': '/paragraph' },
// 46 - End of definition list term item
{ 'type': '/definitionListItem' },
// 47 - Beginning of definition list definition item
{ 'type': 'definitionListItem', 'attributes': { 'style': 'definition' } },
// 48 - Beginning of paragraph
{ 'type': 'paragraph' },
// 49 - Plain "j"
'j',
// 50 - End of paragraph
{ 'type': '/paragraph' },
// 51 - End of definition list definition item
{ 'type': '/definitionListItem' },
// 52 - End of definition list
{ 'type': '/definitionList' }
];
/**
@ -141,18 +173,20 @@ ve.dm.example.data = [
*
* This is part of what a ve.dm.DocumentFragment generates when given linear data.
*
* (16) branch nodes
* (21) branch nodes
* (01) document node
* (01) heading node
* (01) table node
* (01) tableRow node
* (01) tableCell node
* (04) paragraph nodes
* (06) paragraph nodes
* (03) list nodes
* (03) listItem nodes
* (01) preformatted node
* (08) leaf nodes
* (07) text nodes
* (01) definitionList node
* (02) definitionListItem nodes
* (10) leaf nodes
* (09) text nodes
* (01) image node
*/
ve.dm.example.tree = new ve.dm.DocumentNode( [
@ -171,15 +205,15 @@ ve.dm.example.tree = new ve.dm.DocumentNode( [
// 2nd level bullet list item with "f"
new ve.dm.ListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )] )
], ve.dm.example.data[17].attributes )
] )
], ve.dm.example.data[16].attributes )
], ve.dm.example.data[12].attributes )
] )
], ve.dm.example.data[11].attributes ),
new ve.dm.ListNode( [
// Numbered list item with "g"
new ve.dm.ListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )] )
], ve.dm.example.data[26].attributes )
] )
], ve.dm.example.data[25].attributes )
] )
] )
@ -189,6 +223,16 @@ ve.dm.example.tree = new ve.dm.DocumentNode( [
new ve.dm.TextNode( 1 ),
new ve.dm.ImageNode( [], ve.dm.example.data[37].attributes ),
new ve.dm.TextNode( 1 )
] ),
new ve.dm.DefinitionListNode( [
// Definition list term item with "j"
new ve.dm.DefinitionListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )] )
], ve.dm.example.data[42].attributes ),
// Definition list definition item with "k"
new ve.dm.DefinitionListItemNode( [
new ve.dm.ParagraphNode( [new ve.dm.TextNode( 1 )] )
], ve.dm.example.data[47].attributes )
] )
] );
@ -323,6 +367,30 @@ ve.dm.example.getOffsetMap = function( root ) {
// i
lookup( 2 ), // 40 - preformatted
// </pre>
lookup() // 41 - document
lookup(), // 41 - document
// <dl>
lookup( 3 ), // 42 - definitionList
// <dt>
lookup( 3, 0 ), // 43 - definitionListItem
// <p>
lookup( 3, 0, 0 ), // 44 - paragraph
// f
lookup( 3, 0, 0 ), // 45 - paragraph
// </p>
lookup( 3, 0 ), // 46 - definitionListItem
// </dt>
lookup( 3 ), // 47 - definitionList
// <dd>
lookup( 3, 1 ), // 48 - definitionListItem
// <p>
lookup( 3, 1, 0 ), // 49 - paragraph
// f
lookup( 3, 1, 0 ), // 50 - paragraph
// </p>
lookup( 3, 1 ), // 51 - definitionListItem
// </dd>
lookup( 3 ), // 52 - definitionList
// </dl>
lookup() // 53 - document
];
};

View file

@ -41,6 +41,8 @@
<!-- VisualEditor DataModel Nodes -->
<script src="../../modules/ve2/dm/nodes/ve.dm.AlienNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DefinitionListNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DefinitionListItemNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.DocumentNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.HeadingNode.js"></script>
<script src="../../modules/ve2/dm/nodes/ve.dm.ImageNode.js"></script>
@ -62,6 +64,8 @@
<!-- VisualEditor ContentEditable Nodes -->
<script src="../../modules/ve2/ce/nodes/ve.ce.AlienNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DefinitionListNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DefinitionListItemNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.DocumentNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.HeadingNode.js"></script>
<script src="../../modules/ve2/ce/nodes/ve.ce.ImageNode.js"></script>