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 /* 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