Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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 cr.define('options', function() { | |
| 6 const DeletableItemList = options.DeletableItemList; | |
| 7 const DeletableItem = options.DeletableItem; | |
| 8 const ArrayDataModel = cr.ui.ArrayDataModel; | |
| 9 const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel; | |
| 10 const localStrings = new LocalStrings(); | |
| 11 | |
| 12 /** | |
| 13 * Returns the item's height, like offsetHeight but such that it works better | |
| 14 * when the page is zoomed. See the similar calculation in @{code cr.ui.List}. | |
| 15 * This version also accounts for the animation done in this file. | |
| 16 * @param {Element} item The item to get the height of. | |
| 17 * @return {number} The height of the item, calculated with zooming in mind. | |
| 18 */ | |
| 19 function getItemHeight(item) { | |
|
James Hawkins
2011/08/17 03:18:39
Does this need to be refactored into list.js?
Greg Billock
2011/08/17 18:49:50
Seems like a good candidate. Looks like List alrea
| |
| 20 var height = item.style.height; | |
| 21 // Use the fixed animation target height if set, in case the element is | |
| 22 // currently being animated and we'd get an intermediate height below. | |
| 23 if (height && height.substr(-2) == 'px') | |
| 24 return parseInt(height.substr(0, height.length - 2)); | |
| 25 return item.getBoundingClientRect().height; | |
| 26 } | |
| 27 | |
| 28 // Map of parent pathIDs to node objects. | |
| 29 var parentLookup = {}; | |
| 30 | |
| 31 // Pending requests for child information. | |
| 32 var lookupRequests = {}; | |
| 33 | |
|
James Hawkins
2011/08/17 03:18:39
Remove extra blank line.
Greg Billock
2011/08/17 18:49:50
Done.
| |
| 34 | |
| 35 /** | |
| 36 * Creates a new list item for sites data. Note that these are created and | |
|
James Hawkins
2011/08/17 03:18:39
sites?
Greg Billock
2011/08/17 18:49:50
Beats me. :-) Fixed this up. BTW, it looks like th
| |
| 37 * destroyed lazily as they scroll into and out of view, so they must be | |
| 38 * stateless. We cache the expanded item in @{code IntentsList} though, so it | |
| 39 * can keep state. (Mostly just which item is selected.) | |
| 40 * @param {Object} origin Data used to create an intents list item. | |
| 41 * @param {IntentsList} list The list that will contain this item. | |
| 42 * @constructor | |
| 43 * @extends {DeletableItem} | |
| 44 */ | |
| 45 function IntentsListItem(origin, list) { | |
| 46 var listItem = new DeletableItem(null); | |
| 47 listItem.__proto__ = IntentsListItem.prototype; | |
| 48 | |
| 49 listItem.origin = origin; | |
| 50 listItem.list = list; | |
| 51 listItem.decorate(); | |
| 52 | |
| 53 // This hooks up updateOrigin() to the list item, makes the top-level | |
|
James Hawkins
2011/08/17 03:18:39
Is this file a copy/paste/rename of the cookies co
Greg Billock
2011/08/17 18:49:50
Yes, it's a copy-paste. I don't think I've complet
| |
| 54 // tree nodes (i.e., origins) register their IDs in parentLookup, and | |
| 55 // causes them to request their children if they have none. Note that we | |
| 56 // have special logic in the setter for the parent property to make sure | |
| 57 // that we can still garbage collect list items when they scroll out of | |
| 58 // view, even though it appears that we keep a direct reference. | |
| 59 if (origin) { | |
| 60 origin.parent = listItem; | |
| 61 origin.updateOrigin(); | |
| 62 } | |
| 63 | |
| 64 return listItem; | |
| 65 } | |
| 66 | |
| 67 IntentsListItem.prototype = { | |
| 68 __proto__: DeletableItem.prototype, | |
| 69 | |
| 70 /** @inheritDoc */ | |
| 71 decorate: function() { | |
| 72 this.siteChild = this.ownerDocument.createElement('div'); | |
| 73 this.siteChild.className = 'intents-site'; | |
| 74 this.dataChild = this.ownerDocument.createElement('div'); | |
| 75 this.dataChild.className = 'intents-data'; | |
| 76 this.itemsChild = this.ownerDocument.createElement('div'); | |
| 77 this.itemsChild.className = 'intents-items'; | |
| 78 this.infoChild = this.ownerDocument.createElement('div'); | |
| 79 this.infoChild.className = 'intents-details'; | |
| 80 this.infoChild.hidden = true; | |
| 81 var remove = this.ownerDocument.createElement('button'); | |
| 82 remove.textContent = localStrings.getString('remove_intent'); | |
| 83 remove.onclick = this.removeIntent_.bind(this); | |
| 84 this.infoChild.appendChild(remove); | |
| 85 var content = this.contentElement; | |
| 86 content.appendChild(this.siteChild); | |
| 87 content.appendChild(this.dataChild); | |
| 88 content.appendChild(this.itemsChild); | |
| 89 this.itemsChild.appendChild(this.infoChild); | |
| 90 if (this.origin && this.origin.data) { | |
| 91 this.siteChild.textContent = this.origin.data.site; | |
| 92 this.siteChild.setAttribute('title', this.origin.data.site); | |
| 93 } | |
| 94 this.itemList_ = []; | |
| 95 }, | |
| 96 | |
| 97 /** @type {boolean} */ | |
| 98 get expanded() { | |
| 99 return this.expanded_; | |
| 100 }, | |
| 101 set expanded(expanded) { | |
| 102 if (this.expanded_ == expanded) | |
| 103 return; | |
| 104 this.expanded_ = expanded; | |
| 105 if (expanded) { | |
| 106 var oldExpanded = this.list.expandedItem; | |
| 107 this.list.expandedItem = this; | |
| 108 this.updateItems_(); | |
| 109 if (oldExpanded) | |
| 110 oldExpanded.expanded = false; | |
| 111 this.classList.add('show-items'); | |
| 112 this.dataChild.hidden = true; | |
| 113 } else { | |
| 114 if (this.list.expandedItem == this) { | |
| 115 this.list.leadItemHeight = 0; | |
| 116 this.list.expandedItem = null; | |
| 117 } | |
| 118 this.style.height = ''; | |
| 119 this.itemsChild.style.height = ''; | |
| 120 this.classList.remove('show-items'); | |
| 121 this.dataChild.hidden = false; | |
| 122 } | |
| 123 }, | |
| 124 | |
| 125 /** | |
| 126 * The callback for the "remove" button shown when an item is selected. | |
| 127 * Requests that the currently selected intent service be removed. | |
| 128 * @private | |
| 129 */ | |
| 130 removeIntent_: function() { | |
| 131 if (this.selectedIndex_ >= 0) { | |
| 132 var item = this.itemList_[this.selectedIndex_]; | |
| 133 if (item && item.node) | |
| 134 chrome.send('removeIntent', [item.node.pathId]); | |
| 135 } | |
| 136 }, | |
| 137 | |
| 138 /** | |
| 139 * Disable animation within this intents list item, in preparation for | |
| 140 * making changes that will need to be animated. Makes it possible to | |
| 141 * measure the contents without displaying them, to set animation targets. | |
| 142 * @private | |
| 143 */ | |
| 144 disableAnimation_: function() { | |
| 145 this.itemsHeight_ = getItemHeight(this.itemsChild); | |
| 146 this.classList.add('measure-items'); | |
| 147 }, | |
| 148 | |
| 149 /** | |
| 150 * Enable animation after changing the contents of this intents list item. | |
| 151 * See @{code disableAnimation_}. | |
| 152 * @private | |
| 153 */ | |
| 154 enableAnimation_: function() { | |
| 155 if (!this.classList.contains('measure-items')) | |
| 156 this.disableAnimation_(); | |
| 157 this.itemsChild.style.height = ''; | |
| 158 // This will force relayout in order to calculate the new heights. | |
| 159 var itemsHeight = getItemHeight(this.itemsChild); | |
| 160 var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_; | |
| 161 this.itemsChild.style.height = this.itemsHeight_ + 'px'; | |
| 162 // Force relayout before enabling animation, so that if we have | |
| 163 // changed things since the last layout, they will not be animated | |
| 164 // during subsequent layouts. | |
| 165 this.itemsChild.offsetHeight; | |
| 166 this.classList.remove('measure-items'); | |
| 167 this.itemsChild.style.height = itemsHeight + 'px'; | |
| 168 this.style.height = fixedHeight + 'px'; | |
| 169 if (this.expanded) | |
| 170 this.list.leadItemHeight = fixedHeight; | |
| 171 }, | |
| 172 | |
| 173 /** | |
| 174 * Updates the origin summary to reflect changes in its items. | |
| 175 * Both IntentsListItem and IntentsTreeNode implement this API. | |
| 176 * This implementation scans the descendants to update the text. | |
| 177 */ | |
| 178 updateOrigin: function() { | |
| 179 console.log('IntentsListItem.updateOrigin'); | |
| 180 var text = ''; | |
| 181 for (var i = 0; i < this.origin.children.length; ++i) { | |
| 182 if (text.length > 0) | |
| 183 text += ', ' + this.origin.children[i].data.action; | |
| 184 else | |
| 185 text = this.origin.children[i].data.action; | |
| 186 } | |
| 187 this.dataChild.textContent = text; | |
| 188 | |
| 189 if (this.expanded) | |
| 190 this.updateItems_(); | |
| 191 }, | |
| 192 | |
| 193 /** | |
| 194 * Updates the items section to reflect changes, animating to the new state. | |
| 195 * Removes existing contents and calls @{code IntentsTreeNode.createItems}. | |
| 196 * @private | |
| 197 */ | |
| 198 updateItems_: function() { | |
| 199 this.disableAnimation_(); | |
| 200 this.itemsChild.textContent = ''; | |
| 201 this.infoChild.hidden = true; | |
| 202 this.selectedIndex_ = -1; | |
| 203 this.itemList_ = []; | |
| 204 if (this.origin) | |
| 205 this.origin.createItems(this); | |
| 206 this.itemsChild.appendChild(this.infoChild); | |
| 207 this.enableAnimation_(); | |
| 208 }, | |
| 209 | |
| 210 /** | |
| 211 * Append a new intents node "bubble" to this list item. | |
| 212 * @param {IntentsTreeNode} node The intents node to add a bubble for. | |
| 213 * @param {Element} div The DOM element for the bubble itself. | |
| 214 * @return {number} The index the bubble was added at. | |
| 215 */ | |
| 216 appendItem: function(node, div) { | |
| 217 this.itemList_.push({node: node, div: div}); | |
| 218 this.itemsChild.appendChild(div); | |
| 219 return this.itemList_.length - 1; | |
| 220 }, | |
| 221 | |
| 222 /** | |
| 223 * The currently selected intents node ("intents bubble") index. | |
| 224 * @type {number} | |
| 225 * @private | |
| 226 */ | |
| 227 selectedIndex_: -1, | |
| 228 | |
| 229 /** | |
| 230 * Get the currently selected intents node ("intents bubble") index. | |
| 231 * @type {number} | |
| 232 */ | |
| 233 get selectedIndex() { | |
| 234 return this.selectedIndex_; | |
| 235 }, | |
| 236 | |
| 237 /** | |
| 238 * Set the currently selected intents node ("intents bubble") index to | |
| 239 * @{code itemIndex}, unselecting any previously selected node first. | |
| 240 * @param {number} itemIndex The index to set as the selected index. | |
| 241 * TODO: KILL THIS | |
| 242 */ | |
| 243 set selectedIndex(itemIndex) { | |
| 244 // Get the list index up front before we change anything. | |
| 245 var index = this.list.getIndexOfListItem(this); | |
| 246 // Unselect any previously selected item. | |
| 247 if (this.selectedIndex_ >= 0) { | |
| 248 var item = this.itemList_[this.selectedIndex_]; | |
| 249 if (item && item.div) | |
| 250 item.div.removeAttribute('selected'); | |
| 251 } | |
| 252 // Special case: decrementing -1 wraps around to the end of the list. | |
| 253 if (itemIndex == -2) | |
| 254 itemIndex = this.itemList_.length - 1; | |
| 255 // Check if we're going out of bounds and hide the item details. | |
| 256 if (itemIndex < 0 || itemIndex >= this.itemList_.length) { | |
| 257 this.selectedIndex_ = -1; | |
| 258 this.disableAnimation_(); | |
| 259 this.infoChild.hidden = true; | |
| 260 this.enableAnimation_(); | |
| 261 return; | |
| 262 } | |
| 263 // Set the new selected item and show the item details for it. | |
| 264 this.selectedIndex_ = itemIndex; | |
| 265 this.itemList_[itemIndex].div.setAttribute('selected', ''); | |
| 266 this.disableAnimation_(); | |
| 267 this.infoChild.hidden = false; | |
| 268 this.enableAnimation_(); | |
| 269 // If we're near the bottom of the list this may cause the list item to go | |
| 270 // beyond the end of the visible area. Fix it after the animation is done. | |
| 271 var list = this.list; | |
| 272 window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150); | |
| 273 }, | |
| 274 }; | |
| 275 | |
| 276 /** | |
| 277 * {@code IntentsTreeNode}s mirror the structure of the intents tree lazily, | |
| 278 * and contain all the actual data used to generate the | |
| 279 * {@code IntentsListItem}s. | |
| 280 * @param {Object} data The data object for this node. | |
| 281 * @constructor | |
| 282 */ | |
| 283 function IntentsTreeNode(data) { | |
| 284 this.data = data; | |
| 285 this.children = []; | |
| 286 } | |
| 287 | |
| 288 IntentsTreeNode.prototype = { | |
| 289 /** | |
| 290 * Insert an intents tree node at the given index. | |
| 291 * Both IntentsList and IntentsTreeNode implement this API. | |
| 292 * @param {Object} data The data object for the node to add. | |
| 293 * @param {number} index The index at which to insert the node. | |
| 294 */ | |
| 295 insertAt: function(data, index) { | |
| 296 console.log('IntentsTreeNode.insertAt adding ' + | |
| 297 JSON.stringify(data) + ' at ' + index); | |
| 298 var child = new IntentsTreeNode(data); | |
| 299 this.children.splice(index, 0, child); | |
| 300 child.parent = this; | |
| 301 this.updateOrigin(); | |
| 302 }, | |
| 303 | |
| 304 /** | |
| 305 * Remove an intents tree node from the given index. | |
| 306 * Both IntentsList and IntentsTreeNode implement this API. | |
| 307 * @param {number} index The index of the tree node to remove. | |
| 308 */ | |
| 309 remove: function(index) { | |
| 310 if (index < this.children.length) { | |
| 311 this.children.splice(index, 1); | |
| 312 this.updateOrigin(); | |
| 313 } | |
| 314 }, | |
| 315 | |
| 316 /** | |
| 317 * Clears all children. | |
| 318 * Both IntentsList and IntentsTreeNode implement this API. | |
| 319 * It is used by IntentsList.loadChildren(). | |
| 320 */ | |
| 321 clear: function() { | |
| 322 // We might leave some garbage in parentLookup for removed children. | |
| 323 // But that should be OK because parentLookup is cleared when we | |
| 324 // reload the tree. | |
| 325 this.children = []; | |
| 326 this.updateOrigin(); | |
| 327 }, | |
| 328 | |
| 329 /** | |
| 330 * The counter used by startBatchUpdates() and endBatchUpdates(). | |
| 331 * @type {number} | |
| 332 */ | |
| 333 batchCount_: 0, | |
| 334 | |
| 335 /** | |
| 336 * See cr.ui.List.startBatchUpdates(). | |
| 337 * Both IntentsList (via List) and IntentsTreeNode implement this API. | |
| 338 */ | |
| 339 startBatchUpdates: function() { | |
| 340 this.batchCount_++; | |
| 341 }, | |
| 342 | |
| 343 /** | |
| 344 * See cr.ui.List.endBatchUpdates(). | |
| 345 * Both IntentsList (via List) and IntentsTreeNode implement this API. | |
| 346 */ | |
| 347 endBatchUpdates: function() { | |
| 348 if (!--this.batchCount_) | |
| 349 this.updateOrigin(); | |
| 350 }, | |
| 351 | |
| 352 /** | |
| 353 * Requests updating the origin summary to reflect changes in this item. | |
| 354 * Both IntentsListItem and IntentsTreeNode implement this API. | |
| 355 */ | |
| 356 updateOrigin: function() { | |
| 357 if (!this.batchCount_ && this.parent) | |
| 358 this.parent.updateOrigin(); | |
| 359 }, | |
| 360 | |
| 361 /** | |
| 362 * Create the intents services rows for this node. | |
| 363 * Append the rows to @{code item}. | |
| 364 * @param {IntentsListItem} item The intents list item to create items in. | |
| 365 */ | |
| 366 createItems: function(item) { | |
| 367 if (this.children.length > 0) { | |
| 368 for (var i = 0; i < this.children.length; ++i) | |
| 369 this.children[i].createItems(item); | |
| 370 } else if (this.data && !this.data.hasChildren) { | |
| 371 var div = item.ownerDocument.createElement('div'); | |
| 372 div.className = 'intents-item'; | |
| 373 // Help out screen readers and such: this is a clickable thing. | |
| 374 div.setAttribute('role', 'button'); | |
| 375 | |
| 376 var divAction = item.ownerDocument.createElement('div'); | |
| 377 divAction.className = 'intents-item-action'; | |
| 378 divAction.textContent = this.data.action; | |
| 379 div.appendChild(divAction); | |
| 380 | |
| 381 var divTypes = item.ownerDocument.createElement('div'); | |
| 382 divTypes.className = 'intents-item-types'; | |
| 383 var text = ""; | |
| 384 for (var i = 0; i < this.data.types.length; ++i) { | |
| 385 if (text != "") | |
| 386 text += ", "; | |
| 387 text += this.data.types[i]; | |
| 388 } | |
| 389 divTypes.textContent = text; | |
| 390 div.appendChild(divTypes); | |
| 391 | |
| 392 var divUrl = item.ownerDocument.createElement('div'); | |
| 393 divUrl.className = 'intents-item-url'; | |
| 394 divUrl.textContent = this.data.url; | |
| 395 div.appendChild(divUrl); | |
| 396 | |
| 397 var index = item.appendItem(this, div); | |
| 398 div.onclick = function() { | |
| 399 if (item.selectedIndex == index) | |
| 400 item.selectedIndex = -1; | |
| 401 else | |
| 402 item.selectedIndex = index; | |
| 403 }; | |
| 404 } | |
| 405 }, | |
| 406 | |
| 407 /** | |
| 408 * The parent of this intents tree node. | |
| 409 * @type {?IntentsTreeNode|IntentsListItem} | |
| 410 */ | |
| 411 get parent(parent) { | |
| 412 // See below for an explanation of this special case. | |
| 413 if (typeof this.parent_ == 'number') | |
| 414 return this.list_.getListItemByIndex(this.parent_); | |
| 415 return this.parent_; | |
| 416 }, | |
| 417 set parent(parent) { | |
| 418 if (parent == this.parent) | |
| 419 return; | |
| 420 | |
| 421 if (parent instanceof IntentsListItem) { | |
| 422 // If the parent is to be a IntentsListItem, then we keep the reference | |
| 423 // to it by its containing list and list index, rather than directly. | |
| 424 // This allows the list items to be garbage collected when they scroll | |
| 425 // out of view (except the expanded item, which we cache). This is | |
| 426 // transparent except in the setter and getter, where we handle it. | |
| 427 this.parent_ = parent.listIndex; | |
| 428 this.list_ = parent.list; | |
| 429 parent.addEventListener('listIndexChange', | |
| 430 this.parentIndexChanged_.bind(this)); | |
| 431 } else { | |
| 432 this.parent_ = parent; | |
| 433 } | |
| 434 | |
| 435 | |
| 436 if (parent) | |
| 437 parentLookup[this.pathId] = this; | |
| 438 else | |
| 439 delete parentLookup[this.pathId]; | |
| 440 | |
| 441 if (this.data && this.data.hasChildren && | |
| 442 !this.children.length && !lookupRequests[this.pathId]) { | |
| 443 console.log('SENDING loadIntents'); | |
| 444 lookupRequests[this.pathId] = true; | |
| 445 chrome.send('loadIntents', [this.pathId]); | |
| 446 } | |
| 447 }, | |
| 448 | |
| 449 /** | |
| 450 * Called when the parent is a IntentsListItem whose index has changed. | |
| 451 * See the code above that avoids keeping a direct reference to | |
| 452 * IntentsListItem parents, to allow them to be garbage collected. | |
| 453 * @private | |
| 454 */ | |
| 455 parentIndexChanged_: function(event) { | |
| 456 if (typeof this.parent_ == 'number') { | |
| 457 this.parent_ = event.newValue; | |
| 458 // We set a timeout to update the origin, rather than doing it right | |
| 459 // away, because this callback may occur while the list items are | |
| 460 // being repopulated following a scroll event. Calling updateOrigin() | |
| 461 // immediately could trigger relayout that would reset the scroll | |
| 462 // position within the list, among other things. | |
| 463 window.setTimeout(this.updateOrigin.bind(this), 0); | |
| 464 } | |
| 465 }, | |
| 466 | |
| 467 /** | |
| 468 * The intents tree path id. | |
| 469 * @type {string} | |
| 470 */ | |
| 471 get pathId() { | |
| 472 var parent = this.parent; | |
| 473 if (parent && parent instanceof IntentsTreeNode) | |
| 474 return parent.pathId + ',' + this.data.action; | |
| 475 return this.data.site; | |
| 476 }, | |
| 477 }; | |
| 478 | |
| 479 /** | |
| 480 * Creates a new intents list. | |
| 481 * @param {Object=} opt_propertyBag Optional properties. | |
| 482 * @constructor | |
| 483 * @extends {DeletableItemList} | |
| 484 */ | |
| 485 var IntentsList = cr.ui.define('list'); | |
| 486 | |
| 487 IntentsList.prototype = { | |
| 488 __proto__: DeletableItemList.prototype, | |
| 489 | |
| 490 /** @inheritDoc */ | |
| 491 decorate: function() { | |
| 492 DeletableItemList.prototype.decorate.call(this); | |
| 493 this.classList.add('intents-list'); | |
| 494 this.data_ = []; | |
| 495 this.dataModel = new ArrayDataModel(this.data_); | |
| 496 this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this)); | |
| 497 var sm = new ListSingleSelectionModel(); | |
| 498 sm.addEventListener('change', this.cookieSelectionChange_.bind(this)); | |
| 499 sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this)); | |
| 500 this.selectionModel = sm; | |
| 501 }, | |
| 502 | |
| 503 /** | |
| 504 * Handles key down events and looks for left and right arrows, then | |
| 505 * dispatches to the currently expanded item, if any. | |
| 506 * @param {Event} e The keydown event. | |
| 507 * @private | |
| 508 */ | |
| 509 handleKeyLeftRight_: function(e) { | |
| 510 var id = e.keyIdentifier; | |
| 511 if ((id == 'Left' || id == 'Right') && this.expandedItem) { | |
| 512 var cs = this.ownerDocument.defaultView.getComputedStyle(this); | |
| 513 var rtl = cs.direction == 'rtl'; | |
| 514 if ((!rtl && id == 'Left') || (rtl && id == 'Right')) | |
| 515 this.expandedItem.selectedIndex--; | |
| 516 else | |
| 517 this.expandedItem.selectedIndex++; | |
| 518 this.scrollIndexIntoView(this.expandedItem.listIndex); | |
| 519 // Prevent the page itself from scrolling. | |
| 520 e.preventDefault(); | |
| 521 } | |
| 522 }, | |
| 523 | |
| 524 /** | |
| 525 * Called on selection model selection changes. | |
| 526 * @param {Event} ce The selection change event. | |
| 527 * @private | |
| 528 */ | |
| 529 cookieSelectionChange_: function(ce) { | |
| 530 ce.changes.forEach(function(change) { | |
| 531 var listItem = this.getListItemByIndex(change.index); | |
| 532 if (listItem) { | |
| 533 if (!change.selected) { | |
| 534 // We set a timeout here, rather than setting the item unexpanded | |
| 535 // immediately, so that if another item gets set expanded right | |
| 536 // away, it will be expanded before this item is unexpanded. It | |
| 537 // will notice that, and unexpand this item in sync with its own | |
| 538 // expansion. Later, this callback will end up having no effect. | |
| 539 window.setTimeout(function() { | |
| 540 if (!listItem.selected || !listItem.lead) | |
| 541 listItem.expanded = false; | |
| 542 }, 0); | |
| 543 } else if (listItem.lead) { | |
| 544 listItem.expanded = true; | |
| 545 } | |
| 546 } | |
| 547 }, this); | |
| 548 }, | |
| 549 | |
| 550 /** | |
| 551 * Called on selection model lead changes. | |
| 552 * @param {Event} pe The lead change event. | |
| 553 * @private | |
| 554 */ | |
| 555 cookieLeadChange_: function(pe) { | |
| 556 if (pe.oldValue != -1) { | |
| 557 var listItem = this.getListItemByIndex(pe.oldValue); | |
| 558 if (listItem) { | |
| 559 // See cookieSelectionChange_ above for why we use a timeout here. | |
| 560 window.setTimeout(function() { | |
| 561 if (!listItem.lead || !listItem.selected) | |
| 562 listItem.expanded = false; | |
| 563 }, 0); | |
| 564 } | |
| 565 } | |
| 566 if (pe.newValue != -1) { | |
| 567 var listItem = this.getListItemByIndex(pe.newValue); | |
| 568 if (listItem && listItem.selected) | |
| 569 listItem.expanded = true; | |
| 570 } | |
| 571 }, | |
| 572 | |
| 573 /** | |
| 574 * The currently expanded item. Used by IntentsListItem above. | |
| 575 * @type {?IntentsListItem} | |
| 576 */ | |
| 577 expandedItem: null, | |
| 578 | |
| 579 // from cr.ui.List | |
| 580 /** @inheritDoc */ | |
| 581 createItem: function(data) { | |
| 582 // We use the cached expanded item in order to allow it to maintain some | |
| 583 // state (like its fixed height, and which bubble is selected). | |
| 584 if (this.expandedItem && this.expandedItem.origin == data) | |
| 585 return this.expandedItem; | |
| 586 return new IntentsListItem(data, this); | |
| 587 }, | |
| 588 | |
| 589 // from options.DeletableItemList | |
| 590 /** @inheritDoc */ | |
| 591 deleteItemAtIndex: function(index) { | |
| 592 var item = this.data_[index]; | |
| 593 if (item) { | |
| 594 var pathId = item.pathId; | |
| 595 if (pathId) | |
| 596 chrome.send('removeIntent', [pathId]); | |
| 597 } | |
| 598 }, | |
| 599 | |
| 600 /** | |
| 601 * Insert an intents tree node at the given index. | |
| 602 * Both IntentsList and IntentsTreeNode implement this API. | |
| 603 * @param {Object} data The data object for the node to add. | |
| 604 * @param {number} index The index at which to insert the node. | |
| 605 */ | |
| 606 insertAt: function(data, index) { | |
| 607 this.dataModel.splice(index, 0, new IntentsTreeNode(data)); | |
| 608 }, | |
| 609 | |
| 610 /** | |
| 611 * Remove an intents tree node from the given index. | |
| 612 * Both IntentsList and IntentsTreeNode implement this API. | |
| 613 * @param {number} index The index of the tree node to remove. | |
| 614 */ | |
| 615 remove: function(index) { | |
| 616 if (index < this.data_.length) | |
| 617 this.dataModel.splice(index, 1); | |
| 618 }, | |
| 619 | |
| 620 /** | |
| 621 * Clears the list. | |
| 622 * Both IntentsList and IntentsTreeNode implement this API. | |
| 623 * It is used by IntentsList.loadChildren(). | |
| 624 */ | |
| 625 clear: function() { | |
| 626 parentLookup = {}; | |
| 627 this.data_ = []; | |
| 628 this.dataModel = new ArrayDataModel(this.data_); | |
| 629 this.redraw(); | |
| 630 }, | |
| 631 | |
| 632 /** | |
| 633 * Add tree nodes by given parent. | |
| 634 * Note: this method will be O(n^2) in the general case. Use it only to | |
| 635 * populate an empty parent or to insert single nodes to avoid this. | |
| 636 * @param {Object} parent The parent node. | |
| 637 * @param {number} start Start index of where to insert nodes. | |
| 638 * @param {Array} nodesData Nodes data array. | |
| 639 * @private | |
| 640 */ | |
| 641 addByParent_: function(parent, start, nodesData) { | |
| 642 if (!parent) | |
| 643 return; | |
| 644 | |
| 645 parent.startBatchUpdates(); | |
| 646 for (var i = 0; i < nodesData.length; ++i) | |
| 647 parent.insertAt(nodesData[i], start + i); | |
| 648 parent.endBatchUpdates(); | |
| 649 | |
| 650 cr.dispatchSimpleEvent(this, 'change'); | |
| 651 }, | |
| 652 | |
| 653 /** | |
| 654 * Add tree nodes by parent id. | |
| 655 * This is used by intents_view.js. | |
| 656 * Note: this method will be O(n^2) in the general case. Use it only to | |
| 657 * populate an empty parent or to insert single nodes to avoid this. | |
| 658 * @param {string} parentId Id of the parent node. | |
| 659 * @param {number} start Start index of where to insert nodes. | |
| 660 * @param {Array} nodesData Nodes data array. | |
| 661 */ | |
| 662 addByParentId: function(parentId, start, nodesData) { | |
| 663 var parent = parentId ? parentLookup[parentId] : this; | |
| 664 this.addByParent_(parent, start, nodesData); | |
| 665 }, | |
| 666 | |
| 667 /** | |
| 668 * Removes tree nodes by parent id. | |
| 669 * This is used by intents_view.js. | |
| 670 * @param {string} parentId Id of the parent node. | |
| 671 * @param {number} start Start index of nodes to remove. | |
| 672 * @param {number} count Number of nodes to remove. | |
| 673 */ | |
| 674 removeByParentId: function(parentId, start, count) { | |
| 675 var parent = parentId ? parentLookup[parentId] : this; | |
| 676 if (!parent) | |
| 677 return; | |
| 678 | |
| 679 parent.startBatchUpdates(); | |
| 680 while (count-- > 0) | |
| 681 parent.remove(start); | |
| 682 parent.endBatchUpdates(); | |
| 683 | |
| 684 cr.dispatchSimpleEvent(this, 'change'); | |
| 685 }, | |
| 686 | |
| 687 /** | |
| 688 * Loads the immediate children of given parent node. | |
| 689 * This is used by intents_view.js. | |
| 690 * @param {string} parentId Id of the parent node. | |
| 691 * @param {Array} children The immediate children of parent node. | |
| 692 */ | |
| 693 loadChildren: function(parentId, children) { | |
| 694 console.log('Loading intents view: ' + | |
| 695 parentId + ' ' + JSON.stringify(children)); | |
| 696 if (parentId) | |
| 697 delete lookupRequests[parentId]; | |
| 698 var parent = parentId ? parentLookup[parentId] : this; | |
| 699 if (!parent) | |
| 700 return; | |
| 701 | |
| 702 parent.startBatchUpdates(); | |
| 703 parent.clear(); | |
| 704 this.addByParent_(parent, 0, children); | |
| 705 parent.endBatchUpdates(); | |
| 706 }, | |
| 707 }; | |
| 708 | |
| 709 return { | |
| 710 IntentsList: IntentsList | |
| 711 }; | |
| 712 }); | |
| OLD | NEW |