      1 /*
      2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
      3 For licensing, see LICENSE.html or http://ckeditor.com/license
      4 */
      5
      6 (function()
      7 {
      8 	var fireDomWalkerEvent = function( transistionType, fromNode, toNode )
      9 		{
     10 			var eventData = { from : fromNode, to : toNode, type : transistionType };
     11 			this.fire( transistionType, eventData );
     12 			this._.actionEvents.push( eventData );
     13 		};
     14
     15 	CKEDITOR.dom.domWalker = function( node )
     16 	{
     17 		if ( arguments.length < 1 )
     18 			return;
     19
     20 		this._ = { currentNode : node, actionEvents : [], stopFlag : false };
     21 		CKEDITOR.event.implementOn( this );
     22 	};
     23
     24 	CKEDITOR.dom.domWalker.prototype = {
     25 		next : (function()
     26 		{
     27 			var dfsStepForward = function()
     28 			{
     29 				var current = this._.currentNode, next;
     30
     31 				if ( !current )
     32 					return null;
     33
     34 				if ( current.getChildCount && current.getChildCount() > 0 )
     35 				{
     36 					next = current.getChild( 0 );
     37 					fireDomWalkerEvent.call( this, 'down', current, next );
     38 					return next;
     39 				}
     40 				else if ( current.getNext() )
     41 				{
     42 					next = current.getNext();
     43 					fireDomWalkerEvent.call( this, 'sibling', current, next );
     44 					return next;
     45 				}
     46 				else
     47 				{
     48 					var ancestor = current.getParent();
     49 					fireDomWalkerEvent.call( this, 'up', current, ancestor );
     50
     51 					while ( ancestor )
     52 					{
     53 						if ( ancestor.getNext() )
     54 						{
     55 							next = ancestor.getNext();
     56 							fireDomWalkerEvent.call( this, 'sibling', ancestor, next );
     57 							return next;
     58 						}
     59 						else
     60 						{
     61 							next = ancestor.getParent();
     62 							fireDomWalkerEvent.call( this, 'up', ancestor, next );
     63 							ancestor = next;
     64 						}
     65 					}
     66 				}
     67 				return null;
     68 			};
     69
     70 			return function()
     71 			{
     72 				this._.actionEvents = [];
     73 				return {
     74 					node : ( this._.currentNode = dfsStepForward.apply( this ) ),
     75 					events : this._.actionEvents
     76 				};
     77 			};
     78 		})(),
     79
     80 		back : (function()
     81 		{
     82 			var dfsStepBackward = function()
     83 			{
     84 				var current = this._.currentNode, next;
     85
     86 				if ( !current )
     87 					return null;
     88
     89 				if ( current.getPrevious() )
     90 				{
     91 					var lastChild = current.getPrevious();
     92 					fireDomWalkerEvent.call( this, 'sibling', current, lastChild );
     93 					while ( lastChild.getChildCount && lastChild.getChildCount() > 0 )
     94 					{
     95 						next = lastChild.getChild( lastChild.getChildCount() - 1 );
     96 						fireDomWalkerEvent.call( this, 'down', lastChild, next );
     97 						lastChild = next;
     98 					}
     99 					return lastChild;
    100 				}
    101 				else
    102 				{
    103 					next = current.getParent();
    104 					fireDomWalkerEvent.call( this, 'up', current, next );
    105 					return next;
    106 				}
    107 				return null;
    108 			};
    109
    110 			return function()
    111 			{
    112 				this._.actionEvents = [];
    113 				return {
    114 					node : ( this._.currentNode = dfsStepBackward.apply( this ) ),
    115 					events : this._.actionEvents
    116 				};
    117 			};
    118 		})(),
    119
    120 		forward : function( guardFunc )
    121 		{
    122 			var retval;
    123 			this._.stopFlag = false;
    124
    125 			// The default behavior is to stop once the end of document is reached.
    126 			guardFunc = guardFunc || function( evt ) {};
    127
    128 			this.on( 'sibling', guardFunc );
    129 			this.on( 'up', guardFunc );
    130 			this.on( 'down', guardFunc );
    131 			while( ( !retval || retval.node ) && !this._.stopFlag )
    132 			{
    133 				retval = this.next();
    134 				this.fire( 'step', retval );
    135 			}
    136 			this.removeListener( 'sibling', guardFunc );
    137 			this.removeListener( 'up', guardFunc );
    138 			this.removeListener( 'down', guardFunc );
    139 			return retval;
    140 		},
    141
    142 		reverse : function( guardFunc )
    143 		{
    144 			var retval;
    145 			this._.stopFlag = false;
    146
    147 			// The default behavior is top stop once the start of document is reached.
    148 			guardFunc = guardFunc || function( evt ) {};
    149
    150 			this.on( 'sibling', guardFunc );
    151 			this.on( 'up', guardFunc );
    152 			this.on( 'down', guardFunc );
    153 			while( ( !retval || retval.node ) && !this._.stopFlag )
    154 			{
    155 				retval = this.back();
    156 				this.fire( 'step', retval );
    157 			}
    158 			this.removeListener( 'sibling', guardFunc );
    159 			this.removeListener( 'up', guardFunc );
    160 			this.removeListener( 'down', guardFunc );
    161 			return retval;
    162 		},
    163
    164 		stop : function()
    165 		{
    166 			this._.stopFlag = true;
    167 			return this;
    168 		},
    169
    170 		stopped : function()
    171 		{
    172 			return this._.stopFlag;
    173 		},
    174
    175 		setNode : function( node )
    176 		{
    177 			this._.currentNode = node;
    178 			return this;
    179 		}
    180 	};
    181
    182 	/*
    183 	 * Anything whose display computed style is block, list-item, table,
    184 	 * table-row-group, table-header-group, table-footer-group, table-row,
    185 	 * table-column-group, table-column, table-cell, table-caption, or whose node
    186 	 * name is hr, br (when enterMode is br only) is a block boundary.
    187 	 */
    188 	var blockBoundaryDisplayMatch =
    189 	{
    190 		block : 1,
    191 		'list-item' : 1,
    192 		table : 1,
    193 		'table-row-group' : 1,
    194 		'table-header-group' : 1,
    195 		'table-footer-group' : 1,
    196 		'table-row' : 1,
    197 		'table-column-group' : 1,
    198 		'table-column' : 1,
    199 		'table-cell' : 1,
    200 		'table-caption' : 1
    201 	},
    202 		blockBoundaryNodeNameMatch = { hr : 1 };
    203
    204 	CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
    205 	{
    206 		var nodeNameMatches = CKEDITOR.tools.extend( {}, blockBoundaryNodeNameMatch, customNodeNames || {} );
    207
    208 		return blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ||
    209 			nodeNameMatches[ this.getName() ];
    210 	};
    211
    212 	CKEDITOR.dom.domWalker.blockBoundary = function( customNodeNames )
    213 	{
    214 		return function( evt )
    215 		{
    216 			var to = evt.data.to,
    217 				from = evt.data.from;
    218 			if ( to && to.type == CKEDITOR.NODE_ELEMENT )
    219 			{
    220 				if ( to.isBlockBoundary( customNodeNames ) )
    221 				{
    222 					evt.stop();
    223 					this.stop();
    224 					return;
    225 				}
    226 			}
    227 			if ( ( evt.data.type == 'up' || evt.data.type == 'sibling' ) && from && from.type == CKEDITOR.NODE_ELEMENT )
    228 			{
    229 				if ( from.isBlockBoundary( customNodeNames ) )
    230 				{
    231 					evt.stop();
    232 					this.stop();
    233 				}
    234 			}
    235 		};
    236 	};
    237
    238 	CKEDITOR.dom.domWalker.listItemBoundary = function()
    239 	{
    240 		return CKEDITOR.dom.domWalker.blockBoundary( { br : 1 } );
    241 	};
    242 })();
    243