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 |