1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview Nav dot 7 * This is the class for the navigation controls that appear along the bottom 8 * of the NTP. 9 */ 10 11 cr.define('ntp', function() { 12 'use strict'; 13 14 /** 15 * Creates a new navigation dot. 16 * @param {TilePage} page The associated TilePage. 17 * @param {string} title The title of the navigation dot. 18 * @param {bool} titleIsEditable If true, the title can be changed. 19 * @param {bool} animate If true, animates into existence. 20 * @constructor 21 * @extends {HTMLLIElement} 22 */ 23 function NavDot(page, title, titleIsEditable, animate) { 24 var dot = cr.doc.createElement('li'); 25 dot.__proto__ = NavDot.prototype; 26 dot.initialize(page, title, titleIsEditable, animate); 27 28 return dot; 29 } 30 31 NavDot.prototype = { 32 __proto__: HTMLLIElement.prototype, 33 34 initialize: function(page, title, titleIsEditable, animate) { 35 this.className = 'dot'; 36 this.setAttribute('role', 'button'); 37 38 this.page_ = page; 39 40 var selectionBar = this.ownerDocument.createElement('div'); 41 selectionBar.className = 'selection-bar'; 42 this.appendChild(selectionBar); 43 44 // TODO(estade): should there be some limit to the number of characters? 45 this.input_ = this.ownerDocument.createElement('input'); 46 this.input_.setAttribute('spellcheck', false); 47 this.input_.value = title; 48 // Take the input out of the tab-traversal focus order. 49 this.input_.disabled = true; 50 this.appendChild(this.input_); 51 52 this.displayTitle = title; 53 this.titleIsEditable_ = titleIsEditable; 54 55 this.addEventListener('keydown', this.onKeyDown_); 56 this.addEventListener('click', this.onClick_); 57 this.addEventListener('dblclick', this.onDoubleClick_); 58 this.dragWrapper_ = new cr.ui.DragWrapper(this, this); 59 this.addEventListener('webkitTransitionEnd', this.onTransitionEnd_); 60 61 this.input_.addEventListener('blur', this.onInputBlur_.bind(this)); 62 this.input_.addEventListener('mousedown', 63 this.onInputMouseDown_.bind(this)); 64 this.input_.addEventListener('keydown', this.onInputKeyDown_.bind(this)); 65 66 if (animate) { 67 this.classList.add('small'); 68 var self = this; 69 window.setTimeout(function() { 70 self.classList.remove('small'); 71 }, 0); 72 } 73 }, 74 75 /** 76 * @return {TilePage} The associated TilePage. 77 */ 78 get page() { 79 return this.page_; 80 }, 81 82 /** 83 * Sets/gets the display title. 84 * @type {string} title The display name for this nav dot. 85 */ 86 get displayTitle() { 87 return this.title; 88 }, 89 set displayTitle(title) { 90 this.title = this.input_.value = title; 91 }, 92 93 /** 94 * Removes the dot from the page. If |opt_animate| is truthy, we first 95 * transition the element to 0 width. 96 * @param {boolean=} opt_animate Whether to animate the removal or not. 97 */ 98 remove: function(opt_animate) { 99 if (opt_animate) 100 this.classList.add('small'); 101 else 102 this.parentNode.removeChild(this); 103 }, 104 105 /** 106 * Navigates the card slider to the page for this dot. 107 */ 108 switchToPage: function() { 109 ntp.getCardSlider().selectCardByValue(this.page_, true); 110 }, 111 112 /** 113 * Handler for keydown event on the dot. 114 * @param {Event} e The KeyboardEvent. 115 */ 116 onKeyDown_: function(e) { 117 if (e.keyIdentifier == 'Enter') { 118 this.onClick_(e); 119 e.stopPropagation(); 120 } 121 }, 122 123 /** 124 * Clicking causes the associated page to show. 125 * @param {Event} e The click event. 126 * @private 127 */ 128 onClick_: function(e) { 129 this.switchToPage(); 130 // The explicit focus call is necessary because of overriding the default 131 // handling in onInputMouseDown_. 132 if (this.ownerDocument.activeElement != this.input_) 133 this.focus(); 134 135 e.stopPropagation(); 136 }, 137 138 /** 139 * Double clicks allow the user to edit the page title. 140 * @param {Event} e The click event. 141 * @private 142 */ 143 onDoubleClick_: function(e) { 144 if (this.titleIsEditable_) { 145 this.input_.disabled = false; 146 this.input_.focus(); 147 this.input_.select(); 148 } 149 }, 150 151 /** 152 * Prevent mouse down on the input from selecting it. 153 * @param {Event} e The click event. 154 * @private 155 */ 156 onInputMouseDown_: function(e) { 157 if (this.ownerDocument.activeElement != this.input_) 158 e.preventDefault(); 159 }, 160 161 /** 162 * Handle keypresses on the input. 163 * @param {Event} e The click event. 164 * @private 165 */ 166 onInputKeyDown_: function(e) { 167 switch (e.keyIdentifier) { 168 case 'U+001B': // Escape cancels edits. 169 this.input_.value = this.displayTitle; 170 case 'Enter': // Fall through. 171 this.input_.blur(); 172 break; 173 } 174 }, 175 176 /** 177 * When the input blurs, commit the edited changes. 178 * @param {Event} e The blur event. 179 * @private 180 */ 181 onInputBlur_: function(e) { 182 window.getSelection().removeAllRanges(); 183 this.displayTitle = this.input_.value; 184 ntp.saveAppPageName(this.page_, this.displayTitle); 185 this.input_.disabled = true; 186 }, 187 188 shouldAcceptDrag: function(e) { 189 return this.page_.shouldAcceptDrag(e); 190 }, 191 192 /** 193 * A drag has entered the navigation dot. If the user hovers long enough, 194 * we will navigate to the relevant page. 195 * @param {Event} e The MouseOver event for the drag. 196 * @private 197 */ 198 doDragEnter: function(e) { 199 var self = this; 200 function navPageClearTimeout() { 201 self.switchToPage(); 202 self.dragNavTimeout = null; 203 } 204 this.dragNavTimeout = window.setTimeout(navPageClearTimeout, 500); 205 206 this.doDragOver(e); 207 }, 208 209 /** 210 * A dragged element has moved over the navigation dot. Show the correct 211 * indicator and prevent default handling so the <input> won't act as a drag 212 * target. 213 * @param {Event} e The MouseOver event for the drag. 214 * @private 215 */ 216 doDragOver: function(e) { 217 e.preventDefault(); 218 219 if (!this.dragWrapper_.isCurrentDragTarget) 220 ntp.setCurrentDropEffect(e.dataTransfer, 'none'); 221 else 222 this.page_.setDropEffect(e.dataTransfer); 223 }, 224 225 /** 226 * A dragged element has been dropped on the navigation dot. Tell the page 227 * to append it. 228 * @param {Event} e The MouseOver event for the drag. 229 * @private 230 */ 231 doDrop: function(e) { 232 e.stopPropagation(); 233 var tile = ntp.getCurrentlyDraggingTile(); 234 if (tile && tile.tilePage != this.page_) 235 this.page_.appendDraggingTile(); 236 // TODO(estade): handle non-tile drags. 237 238 this.cancelDelayedSwitch_(); 239 }, 240 241 /** 242 * The drag has left the navigation dot. 243 * @param {Event} e The MouseOver event for the drag. 244 * @private 245 */ 246 doDragLeave: function(e) { 247 this.cancelDelayedSwitch_(); 248 }, 249 250 /** 251 * Cancels the timer for page switching. 252 * @private 253 */ 254 cancelDelayedSwitch_: function() { 255 if (this.dragNavTimeout) { 256 window.clearTimeout(this.dragNavTimeout); 257 this.dragNavTimeout = null; 258 } 259 }, 260 261 /** 262 * A transition has ended. 263 * @param {Event} e The transition end event. 264 * @private 265 */ 266 onTransitionEnd_: function(e) { 267 if (e.propertyName === 'max-width' && this.classList.contains('small')) 268 this.parentNode.removeChild(this); 269 }, 270 }; 271 272 return { 273 NavDot: NavDot, 274 }; 275 }); 276