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