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