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() > 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() > 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 	CKEDITOR.dom.domWalker.blockBoundary = function( customNodeNames )
183 	{
184 		return function( evt )
185 		{
186 			/*
187 			 * Anything whose display computed style is block, list-item, table,
188 			 * table-row-group, table-header-group, table-footer-group, table-row,
189 			 * table-column-group, table-column, table-cell, table-caption, or whose node
190 			 * name is hr, br (when enterMode is br only) is a block boundary.
191 			 */
192 			var displayMatches = { block : 1, 'list-item' : 1, table : 1, 'table-row-group' : 1, 'table-header-group' : 1,
193 				'table-footer-group' : 1, 'table-row' : 1, 'table-column-group' : 1, 'table-column' : 1, 'table-cell' : 1,
194 				'table-caption' : 1 },
195 				nodeNameMatches = CKEDITOR.tools.extend( { hr : 1 }, customNodeNames || {} ),
196 				to = evt.data.to,
197 				from = evt.data.from;
198 			if ( to && to.type == CKEDITOR.NODE_ELEMENT )
199 			{
200 				if ( displayMatches[ to.getComputedStyle( 'display' ) ] || nodeNameMatches[ to.getName() ] )
201 				{
202 					evt.stop();
203 					this.stop();
204 					return;
205 				}
206 			}
207 			if ( ( evt.data.type == 'up' || evt.data.type == 'sibling' ) && from && from.type == CKEDITOR.NODE_ELEMENT )
208 			{
209 				if ( displayMatches[ from.getComputedStyle( 'display' ) ] || nodeNameMatches[ from.getName() ] )
210 				{
211 					evt.stop();
212 					this.stop();
213 				}
214 			}
215 		};
216 	};
217
218 	CKEDITOR.dom.domWalker.listItemBoundary = function()
219 	{
220 		return CKEDITOR.dom.domWalker.blockBoundary( { br : 1 } );
221 	};
222 })();
223