1 sc_require("views/view"); 2 3 SC.View.reopen( 4 /** @scope SC.View.prototype */ { 5 // .......................................................... 6 // KEY RESPONDER 7 // 8 9 /** @property 10 YES if the view is currently first responder and the pane the view belongs 11 to is also key pane. While this property is set, you should expect to 12 receive keyboard events. 13 */ 14 isKeyResponder: NO, 15 16 /** 17 This method is invoked just before you lost the key responder status. 18 The passed view is the view that is about to gain keyResponder status. 19 This gives you a chance to do any early setup. Remember that you can 20 gain/lose key responder status either because another view in the same 21 pane is becoming first responder or because another pane is about to 22 become key. 23 24 @param {SC.Responder} responder 25 */ 26 willLoseKeyResponderTo: function(responder) {}, 27 28 /** 29 This method is invoked just before you become the key responder. The 30 passed view is the view that is about to lose keyResponder status. You 31 can use this to do any setup before the view changes. 32 Remember that you can gain/lose key responder status either because 33 another view in the same pane is becoming first responder or because 34 another pane is about to become key. 35 36 @param {SC.Responder} responder 37 */ 38 willBecomeKeyResponderFrom: function(responder) {}, 39 40 /** 41 Invokved just after the responder loses key responder status. 42 @param {SC.Responder} responder 43 */ 44 didLoseKeyResponderTo: function(responder) {}, 45 46 /** 47 Invoked just after the responder gains key responder status. 48 By default, it calls focus on the view root element. For accessibility 49 purposes. 50 51 @param {SC.Responder} responder 52 */ 53 didBecomeKeyResponderFrom: function(responder) {}, 54 55 /** 56 This method will process a key input event, attempting to convert it to 57 an appropriate action method and sending it up the responder chain. The 58 event is converted using the key bindings hashes, (SC.BASE_KEY_BINDINGS 59 and SC.MODIFIED_KEY_BINDINGS) which map key events to method names. If 60 no key binding method is found, then the key event will be passed along 61 to any insertText() method found. 62 63 @param {SC.Event} event 64 @returns {Object} object that handled event, if any 65 */ 66 interpretKeyEvents: function(event) { 67 var codes = event.commandCodes(), 68 cmd = codes[0], 69 chr = codes[1], 70 ret, 71 match, 72 methodName, 73 target, 74 pane, 75 handler; 76 77 if (!cmd && !chr) { return null ; } //nothing to do. 78 79 // if this is a command key, try to do something about it. 80 if (cmd) { 81 match = cmd.match(/[^_]+$/); 82 methodName = SC.MODIFIED_KEY_BINDINGS[cmd]; 83 if (!methodName && match && match.length > 0) { 84 methodName = SC.BASE_KEY_BINDINGS[match[0]]; 85 } 86 if (methodName) { 87 target = this; 88 pane = this.get('pane'); 89 handler = null; 90 while(target && !(handler = target.tryToPerform(methodName, event))){ 91 target = (target===pane)? null: target.get('nextResponder') ; 92 } 93 return handler ; 94 } 95 } 96 97 if (chr && this.respondsTo('insertText')) { 98 // if we haven't returned yet and there is plain text, then do an insert 99 // of the text. Since this is not an action, do not send it up the 100 // responder chain. 101 ret = this.insertText(chr, event); 102 return ret ? (ret===YES ? this : ret) : null ; // map YES|NO => this|nil 103 } 104 105 return null ; //nothing to do. 106 }, 107 108 /** 109 This method is invoked by interpretKeyEvents() when you receive a key 110 event matching some plain text. You can use this to actually insert the 111 text into your application, if needed. 112 113 @param {SC.Event} event 114 @returns {Object} receiver or object that handled event 115 */ 116 insertText: function(chr) { 117 return NO ; 118 }, 119 120 /** 121 Recursively travels down the view hierarchy looking for a view that 122 implements the key equivalent (returning to YES to indicate it handled 123 the event). You can override this method to handle specific key 124 equivalents yourself. 125 126 The keystring is a string description of the key combination pressed. 127 The evt is the event itself. If you handle the equivalent, return YES. 128 Otherwise, you should just return sc_super. 129 130 @param {String} keystring 131 @param {SC.Event} evt 132 @returns {Boolean} 133 */ 134 performKeyEquivalent: function(keystring, evt) { 135 var ret = NO, 136 childViews = this.get('childViews'), 137 len = childViews.length, 138 idx = -1, view ; 139 while (!ret && (++idx < len)) { 140 view = childViews[idx]; 141 142 ret = view.tryToPerform('performKeyEquivalent', keystring, evt); 143 } 144 145 return ret ; 146 }, 147 148 /** 149 The first child of this view for the purposes of tab ordering. If not 150 provided, the first element of childViews is used. Override this if 151 your view displays its child views in an order different from that 152 given in childViews. 153 154 @type SC.View 155 @default null 156 */ 157 firstKeyView: null, 158 159 /** 160 @private 161 162 Actually calculates the firstKeyView as described in firstKeyView. 163 164 @returns {SC.View} 165 */ 166 _getFirstKeyView: function() { 167 // if first was given, just return it 168 var firstKeyView = this.get('firstKeyView'); 169 if(firstKeyView) return firstKeyView; 170 171 // otherwise return the first childView 172 var childViews = this.get('childViews'); 173 if(childViews) return childViews[0]; 174 }, 175 176 /** 177 The last child of this view for the purposes of tab ordering. If not set, can be generated two different ways: 178 1. If firstKeyView is provided, it will be generated by starting from firstKeyView and traversing the childViews nextKeyView properties. 179 2. If firstKeyView is not provided, it will simply return the last element of childViews. 180 181 The first way is not very efficient, so if you provide firstKeyView you should also provide lastKeyView. 182 183 @type SC.View 184 @default null 185 */ 186 lastKeyView: null, 187 188 /** 189 @private 190 191 Actually calculates the lastKeyView as described in lastKeyView. 192 193 @returns {SC.View} 194 */ 195 _getLastKeyView: function() { 196 // if last was given, just return it 197 var lastKeyView = this.get('lastKeyView'); 198 if(lastKeyView) return lastKeyView; 199 200 var view, 201 prev = this.get('firstKeyView'); 202 203 // if first was given but not last, build by starting from first and 204 // traversing until we hit the end. this is obviously the least efficient 205 // way 206 if(prev) { 207 while(view = prev._getNextKeyView()) { 208 prev = view; 209 } 210 211 return prev; 212 } 213 214 // if neither was given, it's more efficient to just return the last 215 // childView 216 else { 217 var childViews = this.get('childViews'); 218 219 if(childViews) return childViews[childViews.length - 1]; 220 } 221 }, 222 223 /** 224 Optionally points to the next key view that should gain focus when tabbing 225 through an interface. If this is not set, then the next key view will 226 be set automatically to the next sibling as defined by its parent's 227 childViews property. 228 229 If any views define this, all of their siblings should define it as well, 230 otherwise undefined behavior may occur. Their parent view should also define 231 a firstKeyView. 232 233 This may also be set to a view that is not a sibling, but once again all 234 views in the chain must define it or undefined behavior will occur. 235 236 Likewise, any view that sets nextKeyView should also set previousKeyView. 237 238 @type SC.View 239 @default null 240 */ 241 242 nextKeyView: undefined, 243 244 /** 245 @private 246 247 Gets the next key view by checking if the user set it and otherwise just 248 getting the next by index in childViews. 249 250 @return {SC.View} 251 */ 252 _getNextKeyView: function() { 253 var pv = this.get('parentView'), 254 nextKeyView = this.get('nextKeyView'); 255 256 // if the parent defines lastKeyView, it takes priority over this views 257 // nextKeyView 258 if(pv && pv.get('lastKeyView') === this) return null; 259 260 // if this view defines a nextKeyView, use it 261 if(nextKeyView !== undefined) return nextKeyView; 262 263 // otherwise generate one based on parent view's childViews 264 if(pv) { 265 var childViews = pv.get('childViews'); 266 return childViews[childViews.indexOf(this) + 1]; 267 } 268 }, 269 270 /** 271 Computes the next valid key view. This is the next key view that 272 acceptsFirstResponder. Computed using depth first search. If the current view 273 is not valid, it will first traverse its children before trying siblings. If 274 the current view is the only valid view, the current view will be returned. Will 275 return null if no valid view can be found. 276 277 @property 278 @type SC.View 279 */ 280 nextValidKeyView: function() { 281 var cur = this, next; 282 while(next !== this) { 283 next = null; 284 285 // only bother to check children if we are visible 286 if(cur.get('isVisibleInWindow')) next = cur._getFirstKeyView(); 287 288 // if we have no children, check our sibling 289 if(!next) next = cur._getNextKeyView(); 290 291 // if we have no children or siblings, unroll up closest parent that has a 292 // next sibling 293 if(!next) { 294 while(cur = cur.get('parentView')) { 295 if(next = cur._getNextKeyView()) break; 296 } 297 } 298 299 // if no parents have a next sibling, start over from the beginning 300 if(!next) { 301 if(!SC.TABBING_ONLY_INSIDE_DOCUMENT) break; 302 else next = this.get('pane'); 303 } 304 305 // if it's a valid firstResponder, we're done! 306 if(next.get('isVisibleInWindow') && next.get('acceptsFirstResponder')) { 307 return next; 308 } 309 // otherwise keep looking 310 cur = next; 311 } 312 // this will only happen if no views are visible and accept first responder 313 return null; 314 }.property('nextKeyView'), 315 316 /** 317 Optionally points to the previous key view that should gain focus when tabbing 318 through an interface. If this is not set, then the previous key view will 319 be set automatically to the previous sibling as defined by its parent's 320 childViews property. 321 322 If any views define this, all of their siblings should define it as well, 323 otherwise undefined behavior may occur. Their parent view should also define 324 a lastKeyView. 325 326 This may also be set to a view that is not a sibling, but once again all 327 views in the chain must define it or undefined behavior will occur. 328 329 Likewise, any view that sets previousKeyView should also set nextKeyView. 330 331 @type SC.View 332 @default null 333 */ 334 previousKeyView: undefined, 335 336 /** 337 @private 338 339 Gets the previous key view by checking if the user set it and otherwise just 340 getting the previous by index in childViews. 341 342 @return {SC.View} 343 */ 344 _getPreviousKeyView: function() { 345 var pv = this.get('parentView'), 346 previousKeyView = this.get('previousKeyView'); 347 348 // if the parent defines firstKeyView, it takes priority over this views 349 // previousKeyView 350 if(pv && pv.get('firstKeyView') === this) return null; 351 352 // if this view defines a previousKeyView, use it 353 if(previousKeyView !== undefined) return previousKeyView; 354 355 // otherwise generate one based on parent view's childViews 356 if(pv) { 357 var childViews = pv.get('childViews'); 358 return childViews[childViews.indexOf(this) - 1]; 359 } 360 }, 361 362 /** 363 Computes the previous valid key view. This is the previous key view that 364 acceptsFirstResponder. Traverse views in the opposite order from 365 nextValidKeyView. If the current view is the pane, tries deepest child. If the 366 current view has a previous view, tries its last child. If this view is the 367 first child, tries the parent. Will return null if no valid view can be 368 found. 369 370 @property 371 @type SC.View 372 */ 373 // TODO: clean this up 374 previousValidKeyView: function() { 375 var cur = this, prev; 376 377 while(prev !== this) { 378 // normally, just try to get previous view's last child 379 if(cur.get('parentView')) prev = cur._getPreviousKeyView(); 380 381 // if we are the pane and address bar tabbing is enabled, trigger it now 382 else if(!SC.TABBING_ONLY_INSIDE_DOCUMENT) break; 383 384 // if we are the pane, get our own last child 385 else prev = cur; 386 387 // loop down to the last valid child 388 if(prev) { 389 do { 390 cur = prev; 391 prev = prev._getLastKeyView(); 392 } while(prev && prev.get('isVisibleInWindow')); 393 394 // if we ended on a null, unroll to the last one 395 // we don't unroll if we ended on a hidden view because we need 396 // to traverse to its previous view next iteration 397 if(!prev) prev = cur; 398 } 399 400 // if there is no previous view, traverse to the parent 401 else prev = cur.get('parentView'); 402 403 // if the view is valid, return it 404 if(prev.get('isVisibleInWindow') && prev.get('acceptsFirstResponder')) return prev; 405 406 // otherwise, try to find its previous valid keyview 407 cur = prev; 408 } 409 410 // if none of the views accept first responder and we make it back to where 411 // we started, just return null 412 return null; 413 }.property('previousKeyView') 414 }); 415 416