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 5cr.define('options.browser_options', function() { 6 /** @const */ var AutocompleteList = cr.ui.AutocompleteList; 7 /** @const */ var InlineEditableItem = options.InlineEditableItem; 8 /** @const */ var InlineEditableItemList = options.InlineEditableItemList; 9 10 /** 11 * Creates a new startup page list item. 12 * @param {Object} pageInfo The page this item represents. 13 * @constructor 14 * @extends {cr.ui.ListItem} 15 */ 16 function StartupPageListItem(pageInfo) { 17 var el = cr.doc.createElement('div'); 18 el.pageInfo_ = pageInfo; 19 StartupPageListItem.decorate(el); 20 return el; 21 } 22 23 /** 24 * Decorates an element as a startup page list item. 25 * @param {!HTMLElement} el The element to decorate. 26 */ 27 StartupPageListItem.decorate = function(el) { 28 el.__proto__ = StartupPageListItem.prototype; 29 el.decorate(); 30 }; 31 32 StartupPageListItem.prototype = { 33 __proto__: InlineEditableItem.prototype, 34 35 /** 36 * Input field for editing the page url. 37 * @type {HTMLElement} 38 * @private 39 */ 40 urlField_: null, 41 42 /** @override */ 43 decorate: function() { 44 InlineEditableItem.prototype.decorate.call(this); 45 46 var pageInfo = this.pageInfo_; 47 48 if (pageInfo.modelIndex == -1) { 49 this.isPlaceholder = true; 50 pageInfo.title = loadTimeData.getString('startupAddLabel'); 51 pageInfo.url = ''; 52 } 53 54 var titleEl = this.ownerDocument.createElement('div'); 55 titleEl.className = 'title'; 56 titleEl.classList.add('favicon-cell'); 57 titleEl.classList.add('weakrtl'); 58 titleEl.textContent = pageInfo.title; 59 if (!this.isPlaceholder) { 60 titleEl.style.backgroundImage = getFaviconImageSet(pageInfo.url); 61 titleEl.title = pageInfo.tooltip; 62 } 63 64 this.contentElement.appendChild(titleEl); 65 66 var urlEl = this.createEditableTextCell(pageInfo.url); 67 urlEl.className = 'url'; 68 urlEl.classList.add('weakrtl'); 69 this.contentElement.appendChild(urlEl); 70 71 var urlField = urlEl.querySelector('input'); 72 urlField.className = 'weakrtl'; 73 urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder'); 74 this.urlField_ = urlField; 75 76 this.addEventListener('commitedit', this.onEditCommitted_); 77 78 var self = this; 79 urlField.addEventListener('focus', function(event) { 80 self.parentNode.autocompleteList.attachToInput(urlField); 81 }); 82 urlField.addEventListener('blur', function(event) { 83 self.parentNode.autocompleteList.detach(); 84 }); 85 86 if (!this.isPlaceholder) 87 this.draggable = true; 88 }, 89 90 /** @override */ 91 get currentInputIsValid() { 92 return this.urlField_.validity.valid; 93 }, 94 95 /** @override */ 96 get hasBeenEdited() { 97 return this.urlField_.value != this.pageInfo_.url; 98 }, 99 100 /** 101 * Called when committing an edit; updates the model. 102 * @param {Event} e The end event. 103 * @private 104 */ 105 onEditCommitted_: function(e) { 106 var url = this.urlField_.value; 107 if (this.isPlaceholder) 108 chrome.send('addStartupPage', [url]); 109 else 110 chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]); 111 }, 112 }; 113 114 var StartupPageList = cr.ui.define('list'); 115 116 StartupPageList.prototype = { 117 __proto__: InlineEditableItemList.prototype, 118 119 /** 120 * An autocomplete suggestion list for URL editing. 121 * @type {AutocompleteList} 122 */ 123 autocompleteList: null, 124 125 /** 126 * The drop position information: "below" or "above". 127 */ 128 dropPos: null, 129 130 /** @override */ 131 decorate: function() { 132 InlineEditableItemList.prototype.decorate.call(this); 133 134 // Listen to drag and drop events. 135 this.addEventListener('dragstart', this.handleDragStart_.bind(this)); 136 this.addEventListener('dragenter', this.handleDragEnter_.bind(this)); 137 this.addEventListener('dragover', this.handleDragOver_.bind(this)); 138 this.addEventListener('drop', this.handleDrop_.bind(this)); 139 this.addEventListener('dragleave', this.handleDragLeave_.bind(this)); 140 this.addEventListener('dragend', this.handleDragEnd_.bind(this)); 141 }, 142 143 /** @override */ 144 createItem: function(pageInfo) { 145 var item = new StartupPageListItem(pageInfo); 146 item.urlField_.disabled = this.disabled; 147 return item; 148 }, 149 150 /** @override */ 151 deleteItemAtIndex: function(index) { 152 chrome.send('removeStartupPages', [index]); 153 }, 154 155 /** 156 * Computes the target item of drop event. 157 * @param {Event} e The drop or dragover event. 158 * @private 159 */ 160 getTargetFromDropEvent_: function(e) { 161 var target = e.target; 162 // e.target may be an inner element of the list item 163 while (target != null && !(target instanceof StartupPageListItem)) { 164 target = target.parentNode; 165 } 166 return target; 167 }, 168 169 /** 170 * Handles the dragstart event. 171 * @param {Event} e The dragstart event. 172 * @private 173 */ 174 handleDragStart_: function(e) { 175 // Prevent dragging if the list is disabled. 176 if (this.disabled) { 177 e.preventDefault(); 178 return false; 179 } 180 181 var target = e.target; 182 // StartupPageListItem should be the only draggable element type in the 183 // page but let's make sure. 184 if (target instanceof StartupPageListItem) { 185 this.draggedItem = target; 186 this.draggedItem.editable = false; 187 e.dataTransfer.effectAllowed = 'move'; 188 // We need to put some kind of data in the drag or it will be 189 // ignored. Use the URL in case the user drags to a text field or the 190 // desktop. 191 e.dataTransfer.setData('text/plain', target.urlField_.value); 192 } 193 }, 194 195 /* 196 * Handles the dragenter event. 197 * @param {Event} e The dragenter event. 198 * @private 199 */ 200 handleDragEnter_: function(e) { 201 e.preventDefault(); 202 }, 203 204 /* 205 * Handles the dragover event. 206 * @param {Event} e The dragover event. 207 * @private 208 */ 209 handleDragOver_: function(e) { 210 var dropTarget = this.getTargetFromDropEvent_(e); 211 // Determines whether the drop target is to accept the drop. 212 // The drop is only successful on another StartupPageListItem. 213 if (!(dropTarget instanceof StartupPageListItem) || 214 dropTarget == this.draggedItem || dropTarget.isPlaceholder) { 215 this.hideDropMarker_(); 216 return; 217 } 218 // Compute the drop postion. Should we move the dragged item to 219 // below or above the drop target? 220 var rect = dropTarget.getBoundingClientRect(); 221 var dy = e.clientY - rect.top; 222 var yRatio = dy / rect.height; 223 var dropPos = yRatio <= .5 ? 'above' : 'below'; 224 this.dropPos = dropPos; 225 this.showDropMarker_(dropTarget, dropPos); 226 e.preventDefault(); 227 }, 228 229 /* 230 * Handles the drop event. 231 * @param {Event} e The drop event. 232 * @private 233 */ 234 handleDrop_: function(e) { 235 var dropTarget = this.getTargetFromDropEvent_(e); 236 237 if (!(dropTarget instanceof StartupPageListItem) || 238 dropTarget.pageInfo_.modelIndex == -1) { 239 return; 240 } 241 242 this.hideDropMarker_(); 243 244 // Insert the selection at the new position. 245 var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_); 246 if (this.dropPos == 'below') 247 newIndex += 1; 248 249 // If there are selected indexes, it was a re-order. 250 if (this.selectionModel.selectedIndexes.length > 0) { 251 chrome.send('dragDropStartupPage', 252 [newIndex, this.selectionModel.selectedIndexes]); 253 return; 254 } 255 256 // Otherwise it was potentially a drop of new data (e.g. a bookmark). 257 var url = e.dataTransfer.getData('url'); 258 if (url) { 259 e.preventDefault(); 260 chrome.send('addStartupPage', [url, newIndex]); 261 } 262 }, 263 264 /** 265 * Handles the dragleave event. 266 * @param {Event} e The dragleave event. 267 * @private 268 */ 269 handleDragLeave_: function(e) { 270 this.hideDropMarker_(); 271 }, 272 273 /** 274 * Handles the dragend event. 275 * @param {Event} e The dragend event. 276 * @private 277 */ 278 handleDragEnd_: function(e) { 279 this.draggedItem.editable = true; 280 this.draggedItem.updateEditState(); 281 }, 282 283 /** 284 * Shows and positions the marker to indicate the drop target. 285 * @param {HTMLElement} target The current target list item of drop. 286 * @param {string} pos 'below' or 'above'. 287 * @private 288 */ 289 showDropMarker_: function(target, pos) { 290 window.clearTimeout(this.hideDropMarkerTimer_); 291 var marker = $('startupPagesListDropmarker'); 292 var rect = target.getBoundingClientRect(); 293 var markerHeight = 6; 294 if (pos == 'above') { 295 marker.style.top = (rect.top - markerHeight / 2) + 'px'; 296 } else { 297 marker.style.top = (rect.bottom - markerHeight / 2) + 'px'; 298 } 299 marker.style.width = rect.width + 'px'; 300 marker.style.left = rect.left + 'px'; 301 marker.style.display = 'block'; 302 }, 303 304 /** 305 * Hides the drop marker. 306 * @private 307 */ 308 hideDropMarker_: function() { 309 // Hide the marker in a timeout to reduce flickering as we move between 310 // valid drop targets. 311 window.clearTimeout(this.hideDropMarkerTimer_); 312 this.hideDropMarkerTimer_ = window.setTimeout(function() { 313 $('startupPagesListDropmarker').style.display = ''; 314 }, 100); 315 }, 316 }; 317 318 return { 319 StartupPageList: StartupPageList 320 }; 321}); 322