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 |