1 /* 2 * Copyright © 2009 Apple Inc. All rights reserved. 3 */ 4 5 /** 6 * @class 7 * @name TKTabControllerDelegate 8 * @since TuneKit 1.0 9 */ 10 11 /** 12 * Indicates that a new controller is becoming the tab controller's selected controller and is about to become visible on screen. 13 * 14 * @name tabControllerWillShowController 15 * @function 16 * 17 * @param {TKTabController} tabController The tab controller. 18 * @param {TKController} controller The controller that is about to be shown by the tab controller. 19 * @memberOf TKTabControllerDelegate.prototype 20 */ 21 const TKTabControllerWillShowController = 'tabControllerWillShowController'; 22 /** 23 * Indicates that a new controller has become the tab controller's selected controller and is fully visible on screen. 24 * 25 * @name tabControllerDidShowController 26 * @function 27 * 28 * @param {TKTabController} tabController The tab controller. 29 * @param {TKController} controller The controller that is being shown by the tab controller. 30 * @memberOf TKTabControllerDelegate.prototype 31 */ 32 const TKTabControllerDidShowController = 'tabControllerDidShowController'; 33 34 /** 35 * The CSS class name applied to an element acting as a tab once it is the selected tab. 36 * @constant 37 * @type String 38 */ 39 const TKTabControllerSelectedCSSClass = 'tk-tab-selected'; 40 41 TKTabController.inherits = TKController; 42 TKTabController.synthetizes = ['selectedController', 'selectedIndex', 'tabsSelector']; 43 44 /** 45 * @class 46 * 47 * <p>A tab controller allows to bind a series of elements within the view, the tabs, to display each a given controller, the tab controller only 48 * allowing a single tab to be selected at once. Tabs are specified using the {@link #tabsSelector} CSS selector, and a controller for each tab needs to be 49 * stored in the {@link #controllers} array. By implementing the {@link TKTabControllerDelegate} protocol, a tab controller's {@link #delegate} can track as 50 * the user navigates between tabs. At any given time, the {@link #selectedController} property allows to find out which of the controllers is currently 51 * selected.</p> 52 * 53 * @extends TKController 54 * @since TuneKit 1.0 55 * 56 * @param {Object} data A hash of properties to use as this object is initialized. 57 */ 58 function TKTabController (data) { 59 this._tabsSelector = null; 60 this._selectedController = null; 61 /** 62 * The controllers managed by this tab controller, ordered in the same way as the elements matched by {@link #tabsSelector} are. 63 * @type Array 64 */ 65 this.controllers = []; 66 this.tabs = []; 67 /** 68 * The delegate for this tab controller, an object implementing the {@link TKTabControllerDelegate} protocol. 69 * @type Object 70 */ 71 this.delegate = null; 72 this.previousController = null; 73 // 74 this.busy = false; 75 // 76 this.callSuper(data); 77 }; 78 79 /* ==================== Additional View Processing ==================== */ 80 81 TKTabController.prototype.processView = function () { 82 this.callSuper(); 83 // 84 this.host = this._view.appendChild(document.createElement('div')); 85 this.host.addClassName('tk-tab-contents'); 86 // restore properties that have not been set yet since construction 87 this.restoreProperty('tabsSelector'); 88 this.restoreProperty('selectedIndex'); 89 }; 90 91 /* ==================== Synthetized Properties ==================== */ 92 93 /** 94 * @name TKTabController.prototype 95 * @property {String} tabsSelector A CSS selector matching the elements within the controller's view that should act as triggers to display the matching 96 * controller in the {@link #controllers} array. 97 */ 98 TKTabController.prototype.setTabsSelector = function (tabsSelector) { 99 if (tabsSelector === null) { 100 return; 101 } 102 // forget the old tabs 103 for (var i = 0; i < this.tabs.length; i++) { 104 var element = this.tabs[i]; 105 element._tabIndex = undefined; 106 element._controller = undefined; 107 this.removeNavigableElement(element); 108 } 109 // get the new tabs 110 this._tabsSelector = tabsSelector; 111 this.tabs = this._view.querySelectorAll(this._tabsSelector); 112 // nothing to do if we don't have tabs 113 if (this.tabs.length < 1) { 114 return; 115 } 116 for (var i = 0; i < this.tabs.length; i++) { 117 var tab = this.tabs[i]; 118 tab._tabIndex = i; 119 tab._controller = this; 120 this.addNavigableElement(tab); 121 } 122 // reset to the first tab, unless we have an archived one 123 var archived_index = this.getArchivedProperty('selectedIndex'); 124 this.selectedIndex = (archived_index === undefined || archived_index < 0) ? 0 : archived_index; 125 }; 126 127 /* ==================== Keyboard Handling ==================== */ 128 129 TKTabController.prototype.getNavigableElements = function () { 130 return this._navigableElements.concat(this.selectedController.navigableElements); 131 }; 132 133 TKTabController.prototype.handleEvent = function (event) { 134 this.callSuper(event); 135 // 136 if (event.currentTarget._tabIndex !== undefined) { 137 if (event.type == 'highlight') { 138 this.selectedIndex = event.currentTarget._tabIndex; 139 } 140 } 141 }; 142 143 TKTabController.prototype.elementWasActivated = function (element) { 144 this.callSuper(element); 145 // tab was activated 146 if (element._tabIndex !== undefined) { 147 // highlight in case we weren't already 148 if (this.highlightedElement !== element) { 149 TKSpatialNavigationManager.sharedManager.highlightElement(element); 150 } 151 // notify we were activated 152 else { 153 this.tabAtIndexWasActivated(element._tabIndex); 154 } 155 } 156 }; 157 158 /** 159 * Indicates that a new tab has been activated at the given index. 160 * 161 * @param {int} index The index for the tab that was just activated. 162 */ 163 TKTabController.prototype.tabAtIndexWasActivated = function (index) {}; 164 165 /* ==================== Controllers ==================== */ 166 167 /** 168 * @name TKTabController.prototype 169 * @property {int} selectedIndex The index of the selected tab. 170 */ 171 TKTabController.prototype.getSelectedIndex = function () { 172 return (this._selectedController === null) ? -1 : this.controllers.indexOf(this._selectedController); 173 }; 174 175 TKTabController.prototype.setSelectedIndex = function (index) { 176 var selected_index = this.selectedIndex; 177 if (index !== selected_index && index >= 0 && index < this.controllers.length) { 178 // move to the new controller 179 this.selectedController = this.controllers[index]; 180 } 181 }; 182 183 /** 184 * @name TKTabController.prototype 185 * @property {TKController} selectedController The selected controller. 186 */ 187 TKTabController.prototype.setSelectedController = function (controller) { 188 var selected_index = this.controllers.indexOf(controller); 189 // do nothing if we don't know about such a controller, or we're already on this controller 190 if (controller === this._selectedController || selected_index == -1) { 191 return; 192 } 193 // clean up before starting a new transition 194 if (this.busy) { 195 this.transitionDidComplete(null); 196 } 197 // 198 TKTransaction.begin(); 199 // mark that a transition is now in progress 200 this.busy = true; 201 // get pointers to object we'll manipulate 202 var previous_controller = this._selectedController; 203 var next_view = controller.view; 204 // fire delegate saying we're moving to a new controller 205 if (TKUtils.objectHasMethod(this.delegate, TKTabControllerWillShowController)) { 206 this.delegate[TKTabControllerWillShowController](this, controller); 207 } 208 // track this is our newly selected controller 209 this._selectedController = controller; 210 // notify of upcoming change and update tabs styling 211 if (previous_controller !== null) { 212 previous_controller._viewWillDisappear(); 213 previous_controller.viewWillDisappear(); 214 } 215 controller._viewWillAppear(); 216 controller.viewWillAppear(); 217 // add it to the tree 218 this.host.appendChild(controller.view); 219 // transition 220 var manager = TKSpatialNavigationManager.sharedManager; 221 if (previous_controller !== null) { 222 // unregister the old controller 223 previous_controller.parentController = null; 224 manager.unregisterController(previous_controller); 225 // remove the selected CSS class from the previous tab, if any 226 this.tabs[this.controllers.indexOf(previous_controller)].removeClassName(TKTabControllerSelectedCSSClass); 227 this.transitionToController(previous_controller, controller); 228 } 229 else { 230 this.busy = true; 231 this.transitionDidComplete(); 232 // highlight the element 233 if (this.highlightedElement === null) { 234 this.highlightedElement = this.tabs[this.selectedIndex] 235 } 236 } 237 // add the selected CSS class to the new controller's tab 238 this.tabs[selected_index].addClassName(TKTabControllerSelectedCSSClass) 239 // also ensure that element has highlight 240 manager.highlightElement(this.tabs[selected_index]); 241 // and that the new controller is registered for navigation 242 controller.parentController = this; 243 manager.registerController(controller); 244 // 245 TKTransaction.commit(); 246 }; 247 248 /* ==================== Transition ==================== */ 249 250 TKTabController.prototype.transitionToController = function (previous_controller, top_controller) { 251 // record some parameters that we will need at the end of the transition 252 this.previousController = previous_controller; 253 // figure out transitions 254 if (previous_controller !== null) { 255 previous_controller.view.applyTransition(previous_controller.becomesInactiveTransition, false); 256 } 257 var top_controller_transition = top_controller.becomesActiveTransition; 258 top_controller_transition.delegate = this; 259 top_controller.view.applyTransition(top_controller_transition, false); 260 }; 261 262 TKTabController.prototype.transitionDidComplete = function (transition) { 263 // update the highlightable items and notify of completed change 264 if (this.previousController !== null) { 265 if (this.previousController.view.parentNode === this.host) { 266 this.host.removeChild(this.previousController.view); 267 } 268 this.previousController._viewDidDisappear(); 269 this.previousController.viewDidDisappear(); 270 } 271 this._selectedController._viewDidAppear(); 272 this._selectedController.viewDidAppear(); 273 // fire delegate saying we've moved to a new controller 274 if (TKUtils.objectHasMethod(this.delegate, TKTabControllerDidShowController)) { 275 this.delegate[TKTabControllerDidShowController](this, this._selectedController); 276 } 277 // not busy anymore 278 this.busy = false; 279 }; 280 281 /* ==================== Archival ==================== */ 282 283 TKTabController.prototype.archive = function () { 284 var archive = this.callSuper(); 285 archive.selectedIndex = this.selectedIndex; 286 return archive; 287 }; 288 289 TKClass(TKTabController); 290