Added basic ve.ce nodes

* Also removed beforeSplice and afterSplice in favor of just plain splice which is the same as afterSplice used to be - beforeSplice was never used and it was making things more complex looking than needed

Change-Id: Icbbc57eac73a2a206ba35409ab57b3d1a49ab1a5
This commit is contained in:
Trevor Parscal 2012-04-30 16:58:41 -07:00
parent aaa322642b
commit c2d4a2d928
16 changed files with 230 additions and 59 deletions

View file

@ -0,0 +1,99 @@
/**
* ContentEditable node that can have branch or leaf children.
*
* @class
* @abstract
* @constructor
* @extends {ve.BranchNode}
* @extends {ve.ce.Node}
* @param model {ve.dm.BranchNode} Model to observe
* @param {jQuery} [$element] Element to use as a container
*/
ve.ce.BranchNode = function( model, $element ) {
// Inheritance
ve.BranchNode.call( this );
ve.ce.Node.call( this, model, $element );
// Properties
this.$ = $element || $( '<div></div>' );
// Events
this.model.addListenerMethod( 'splice', this, 'onSplice' );
// Initialization
this.onAfterSplice.apply( this, [0].concat( model.getChildren() ) );
};
/* Methods */
ve.ce.BranchNode.prototype.convertDomElement = function( type ) {
// Create new element
var $new = $( '<' + type + '></' + type + '>' );
// Copy classes
$new.attr( 'class', this.$.attr( 'class' ) );
// Move contents
$new.append( this.$.contents() );
// Swap elements
this.$.replaceWith( $new );
// Use new element from now on
this.$ = $new;
};
ve.ce.BranchNode.prototype.onSplice = function( index, howmany ) {
var i,
length,
args = Array.prototype.slice.call( arguments, 0 );
// Convert models to views and attach them to this node
if ( args.length >= 3 ) {
for ( i = 2, length = args.length; i < length; i++ ) {
args[i] = args[i].createView();
}
}
this.emit.apply( this, ['beforeSplice'].concat( args ) );
var removals = this.children.splice.apply( this.children, args );
for ( i = 0, length = removals.length; i < length; i++ ) {
removals[i].detach();
removals[i].removeListener( 'update', this.emitUpdate );
// Update DOM
removals[i].$.detach();
}
if ( args.length >= 3 ) {
var $target;
if ( index ) {
// Get the element before the insertion point
$anchor = this.$.children().eq( index - 1 );
}
for ( i = args.length - 1; i >= 2; i-- ) {
args[i].attach( this );
args[i].on( 'update', this.emitUpdate );
if ( index ) {
$anchor.after( args[i].$ );
} else {
this.$.prepend( args[i].$ );
}
}
}
this.emit.apply( this, ['afterSplice'].concat( args ) );
if ( args.length >= 3 ) {
for ( i = 2, length = args.length; i < length; i++ ) {
args[i].render();
}
}
this.emit( 'update' );
};
/**
* Render content.
*
* @method
*/
ve.ce.BranchNode.prototype.render = function() {
for ( var i = 0; i < this.children.length; i++ ) {
this.children[i].render();
}
};
/* Inheritance */
ve.extendClass( ve.ce.BranchNode, ve.BranchNode );
ve.extendClass( ve.ce.BranchNode, ve.ce.Node );

View file

@ -0,0 +1,31 @@
/**
* ContentEditable node that can not have any children.
*
* @class
* @abstract
* @constructor
* @extends {ve.LeafNode}
* @extends {ve.ce.Node}
* @param model {ve.dm.LeafNode} Model to observe
*/
ve.ce.LeafNode = function( model ) {
// Inheritance
ve.LeafNode.call( this );
ve.ce.Node.call( this, model );
};
/* Methods */
/**
* Render content.
*
* @method
*/
ve.ce.LeafNode.prototype.render = function() {
//
};
/* Inheritance */
ve.extendClass( ve.ce.LeafNode, ve.LeafNode );
ve.extendClass( ve.ce.LeafNode, ve.ce.Node );

View file

@ -0,0 +1,67 @@
/**
* Generic ContentEditable node.
*
* @class
* @abstract
* @constructor
* @extends {ve.Node}
* @param {ve.dm.Node} model Model to observe
*/
ve.ce.Node = function( model ) {
// Inheritance
ve.Node.call( this );
// Properties
this.model = model;
this.parent = null;
};
/* Methods */
/**
* Gets a reference to the model this node observes.
*
* @method
* @returns {ve.dm.Node} Reference to the model this node observes
*/
ve.ce.Node.prototype.getModel = function() {
return this.model;
};
/**
* Gets a reference to this node's parent.
*
* @method
* @returns {ve.ce.Node} Reference to this node's parent
*/
ve.ce.Node.prototype.getParent = function() {
return this.parent;
};
/**
* Attaches node as a child to another node.
*
* @method
* @param {ve.ce.Node} parent Node to attach to
* @emits attach (parent)
*/
ve.ce.Node.prototype.attach = function( parent ) {
this.parent = parent;
this.emit( 'attach', parent );
};
/**
* Detaches node from it's parent.
*
* @method
* @emits detach (parent)
*/
ve.ce.Node.prototype.detach = function() {
var parent = this.parent;
this.parent = null;
this.emit( 'detach', parent );
};
/* Inheritance */
ve.extendClass( ve.ce.Node, ve.Node );

View file

@ -1,5 +1,5 @@
/** /**
* VisualEditor ContentEditable namespace. * ContentEditable namespace.
* *
* All classes and functions will be attached to this object to keep the global namespace clean. * All classes and functions will be attached to this object to keep the global namespace clean.
*/ */

View file

@ -1,5 +1,5 @@
/** /**
* Data model node that can have branch or twig children. * DataModel node that can have branch or leaf children.
* *
* @class * @class
* @abstract * @abstract
@ -48,8 +48,7 @@ ve.dm.BranchNode.prototype.setRoot = function( root ) {
* @method * @method
* @param {ve.dm.BranchNode} childModel Item to add * @param {ve.dm.BranchNode} childModel Item to add
* @returns {Integer} New number of children * @returns {Integer} New number of children
* @emits beforeSplice (index, 0, [childModel]) * @emits splice (index, 0, [childModel])
* @emits afterSplice (index, 0, [childModel])
* @emits update * @emits update
*/ */
ve.dm.BranchNode.prototype.push = function( childModel ) { ve.dm.BranchNode.prototype.push = function( childModel ) {
@ -62,8 +61,7 @@ ve.dm.BranchNode.prototype.push = function( childModel ) {
* *
* @method * @method
* @returns {ve.dm.BranchNode} Removed childModel * @returns {ve.dm.BranchNode} Removed childModel
* @emits beforeSplice (index, 1, []) * @emits splice (index, 1, [])
* @emits afterSplice (index, 1, [])
* @emits update * @emits update
*/ */
ve.dm.BranchNode.prototype.pop = function() { ve.dm.BranchNode.prototype.pop = function() {
@ -80,8 +78,7 @@ ve.dm.BranchNode.prototype.pop = function() {
* @method * @method
* @param {ve.dm.BranchNode} childModel Item to add * @param {ve.dm.BranchNode} childModel Item to add
* @returns {Integer} New number of children * @returns {Integer} New number of children
* @emits beforeSplice (0, 0, [childModel]) * @emits splice (0, 0, [childModel])
* @emits afterSplice (0, 0, [childModel])
* @emits update * @emits update
*/ */
ve.dm.BranchNode.prototype.unshift = function( childModel ) { ve.dm.BranchNode.prototype.unshift = function( childModel ) {
@ -94,8 +91,7 @@ ve.dm.BranchNode.prototype.unshift = function( childModel ) {
* *
* @method * @method
* @returns {ve.dm.BranchNode} Removed childModel * @returns {ve.dm.BranchNode} Removed childModel
* @emits beforeSplice (0, 1, []) * @emits splice (0, 1, [])
* @emits afterSplice (0, 1, [])
* @emits update * @emits update
*/ */
ve.dm.BranchNode.prototype.shift = function() { ve.dm.BranchNode.prototype.shift = function() {
@ -114,8 +110,7 @@ ve.dm.BranchNode.prototype.shift = function() {
* @param {Integer} howmany Number of nodes to remove * @param {Integer} howmany Number of nodes to remove
* @param {ve.dm.BranchNode} [...] Variadic list of nodes to insert * @param {ve.dm.BranchNode} [...] Variadic list of nodes to insert
* @returns {ve.dm.BranchNode[]} Removed nodes * @returns {ve.dm.BranchNode[]} Removed nodes
* @emits beforeSplice (index, howmany, [...]) * @emits splice (index, howmany, [...])
* @emits afterSplice (index, howmany, [...])
* @emits update * @emits update
*/ */
ve.dm.BranchNode.prototype.splice = function( index, howmany ) { ve.dm.BranchNode.prototype.splice = function( index, howmany ) {
@ -123,7 +118,6 @@ ve.dm.BranchNode.prototype.splice = function( index, howmany ) {
length, length,
args = Array.prototype.slice.call( arguments, 0 ), args = Array.prototype.slice.call( arguments, 0 ),
diff = 0; diff = 0;
this.emit.apply( this, ['beforeSplice'].concat( args ) );
if ( args.length >= 3 ) { if ( args.length >= 3 ) {
length = args.length; length = args.length;
for ( i = 2; i < length; i++ ) { for ( i = 2; i < length; i++ ) {
@ -139,7 +133,7 @@ ve.dm.BranchNode.prototype.splice = function( index, howmany ) {
diff -= removals[i].getOuterLength(); diff -= removals[i].getOuterLength();
} }
this.adjustLength( diff, true ); this.adjustLength( diff, true );
this.emit.apply( this, ['afterSplice'].concat( args ) ); this.emit.apply( this, ['splice'].concat( args ) );
this.emit( 'update' ); this.emit( 'update' );
return removals; return removals;
}; };

View file

@ -1,5 +1,5 @@
/** /**
* Document object. * DataModel document.
* *
* @class * @class
* @constructor * @constructor

View file

@ -1,5 +1,5 @@
/** /**
* Document fragment. * DataModel document fragment.
* *
* @class * @class
* @constructor * @constructor

View file

@ -1,5 +1,5 @@
/** /**
* Data model node that can not have children. * DataModel node that can not have children.
* *
* @class * @class
* @abstract * @abstract

View file

@ -1,5 +1,5 @@
/** /**
* Creates an ve.dm.Node object. * Generic DataModel node.
* *
* @class * @class
* @abstract * @abstract

View file

@ -1,5 +1,5 @@
/** /**
* Creates an ve.dm.Transaction object. * DataModel transaction.
* *
* @class * @class
* @constructor * @constructor

View file

@ -1,5 +1,5 @@
/** /**
* Class that processes a transaction. * DataModel transaction processor.
* *
* This class reads operations from a transaction and applies them one by one. It's not intended * This class reads operations from a transaction and applies them one by one. It's not intended
* to be used directly; use the static functions ve.dm.TransactionProcessor.commit() and .rollback() * to be used directly; use the static functions ve.dm.TransactionProcessor.commit() and .rollback()

View file

@ -1,5 +1,5 @@
/** /**
* VisualEditor DataModel namespace. * DataModel namespace.
* *
* All classes and functions will be attached to this object to keep the global namespace clean. * All classes and functions will be attached to this object to keep the global namespace clean.
*/ */

View file

@ -1,5 +1,5 @@
/** /**
* Mixin for branch node functionality * Mixin for branch nodes.
* *
* Branch nodes are immutable, which is why there are no methods for adding or removing children. * Branch nodes are immutable, which is why there are no methods for adding or removing children.
* DataModel classes will add this functionality, and other subclasses will implement behavior that * DataModel classes will add this functionality, and other subclasses will implement behavior that

View file

@ -1,5 +1,5 @@
/** /**
* Mixin for leaf nodes * Mixin for leaf nodes.
* *
* @class * @class
* @abstract * @abstract

View file

@ -1,5 +1,5 @@
/** /**
* Creates an ve.Node object. * Generic node.
* *
* @class * @class
* @abstract * @abstract

View file

@ -22,82 +22,62 @@ test( 'setRoot', 3, function() {
strictEqual( node1.getRoot(), node4 ); strictEqual( node1.getRoot(), node4 );
} ); } );
test( 'push', 4, function() { test( 'push', 3, function() {
var node1 = new ve.dm.BranchNodeStub(), var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(), node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1] ); node3 = new ve.dm.BranchNodeStub( [node1] );
node3.on( 'beforeSplice', function() { node3.on( 'splice', function() {
// Will be called 1 time // Will be called 1 time
ok( true, 'beforeSplice was emitted' ); ok( true, 'splice was emitted' );
} );
node3.on( 'afterSplice', function() {
// Will be called 1 time
ok( true, 'afterSplice was emitted' );
} ); } );
strictEqual( node3.push( node2 ), 2 ); strictEqual( node3.push( node2 ), 2 );
deepEqual( node3.getChildren(), [node1, node2] ); deepEqual( node3.getChildren(), [node1, node2] );
} ); } );
test( 'pop', 4, function() { test( 'pop', 3, function() {
var node1 = new ve.dm.BranchNodeStub(), var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(), node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1, node2] ); node3 = new ve.dm.BranchNodeStub( [node1, node2] );
node3.on( 'beforeSplice', function() { node3.on( 'splice', function() {
// Will be called 1 time // Will be called 1 time
ok( true, 'beforeSplice was emitted' ); ok( true, 'splice was emitted' );
} );
node3.on( 'afterSplice', function() {
// Will be called 1 time
ok( true, 'afterSplice was emitted' );
} ); } );
strictEqual( node3.pop(), node2 ); strictEqual( node3.pop(), node2 );
deepEqual( node3.getChildren(), [node1] ); deepEqual( node3.getChildren(), [node1] );
} ); } );
test( 'unshift', 4, function() { test( 'unshift', 3, function() {
var node1 = new ve.dm.BranchNodeStub(), var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(), node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1] ); node3 = new ve.dm.BranchNodeStub( [node1] );
node3.on( 'beforeSplice', function() { node3.on( 'splice', function() {
// Will be called 1 time // Will be called 1 time
ok( true, 'beforeSplice was emitted' ); ok( true, 'splice was emitted' );
} );
node3.on( 'afterSplice', function() {
// Will be called 1 time
ok( true, 'afterSplice was emitted' );
} ); } );
strictEqual( node3.unshift( node2 ), 2 ); strictEqual( node3.unshift( node2 ), 2 );
deepEqual( node3.getChildren(), [node2, node1] ); deepEqual( node3.getChildren(), [node2, node1] );
} ); } );
test( 'shift', 4, function() { test( 'shift', 3, function() {
var node1 = new ve.dm.BranchNodeStub(), var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(), node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub( [node1, node2] ); node3 = new ve.dm.BranchNodeStub( [node1, node2] );
node3.on( 'beforeSplice', function() { node3.on( 'splice', function() {
// Will be called 1 time // Will be called 1 time
ok( true, 'beforeSplice was emitted' ); ok( true, 'splice was emitted' );
} );
node3.on( 'afterSplice', function() {
// Will be called 1 time
ok( true, 'afterSplice was emitted' );
} ); } );
strictEqual( node3.shift(), node1 ); strictEqual( node3.shift(), node1 );
deepEqual( node3.getChildren(), [node2] ); deepEqual( node3.getChildren(), [node2] );
} ); } );
test( 'splice', 12, function() { test( 'splice', 9, function() {
var node1 = new ve.dm.BranchNodeStub(), var node1 = new ve.dm.BranchNodeStub(),
node2 = new ve.dm.BranchNodeStub(), node2 = new ve.dm.BranchNodeStub(),
node3 = new ve.dm.BranchNodeStub(), node3 = new ve.dm.BranchNodeStub(),
node4 = new ve.dm.BranchNodeStub( [node1, node2] ); node4 = new ve.dm.BranchNodeStub( [node1, node2] );
node4.on( 'beforeSplice', function() { node4.on( 'splice', function() {
// Will be called 3 times // Will be called 3 times
ok( true, 'beforeSplice was emitted' ); ok( true, 'splice was emitted' );
} );
node4.on( 'afterSplice', function() {
// Will be called 3 times
ok( true, 'afterSplice was emitted' );
} ); } );
// Insert branch // Insert branch
deepEqual( node4.splice( 1, 0, node3 ), [] ); deepEqual( node4.splice( 1, 0, node3 ), [] );