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