| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of swarmlib; | 5 part of swarmlib; |
| 6 | 6 |
| 7 // This file contains View framework classes. | 7 // This file contains View framework classes. |
| 8 // As it grows, it may need to be split into multiple files. | 8 // As it grows, it may need to be split into multiple files. |
| 9 | 9 |
| 10 /** A factory that creates a view from a data model. */ | 10 /** A factory that creates a view from a data model. */ |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 /** The width of the created view for a specific data model. */ | 24 /** The width of the created view for a specific data model. */ |
| 25 int getWidth(D item); | 25 int getWidth(D item); |
| 26 | 26 |
| 27 /** The height of the created view for a specific data model. */ | 27 /** The height of the created view for a specific data model. */ |
| 28 int getHeight(D item); | 28 int getHeight(D item); |
| 29 } | 29 } |
| 30 | 30 |
| 31 /** A collection of event listeners. */ | 31 /** A collection of event listeners. */ |
| 32 class EventListeners { | 32 class EventListeners { |
| 33 var listeners; | 33 var listeners; |
| 34 EventListeners() { | 34 EventListeners() { |
| 35 listeners = new List(); | 35 listeners = new List(); |
| 36 } | 36 } |
| 37 | 37 |
| 38 void addListener(listener) { | 38 void addListener(listener) { |
| 39 listeners.add(listener); | 39 listeners.add(listener); |
| 40 } | 40 } |
| 41 | 41 |
| 42 void fire(var event) { | 42 void fire(var event) { |
| 43 for (final listener in listeners) { | 43 for (final listener in listeners) { |
| 44 listener(event); | 44 listener(event); |
| 45 } | 45 } |
| 46 } | 46 } |
| 47 } | 47 } |
| 48 | 48 |
| 49 | |
| 50 /** | 49 /** |
| 51 * Private view class used to store placeholder views for detatched ListView | 50 * Private view class used to store placeholder views for detatched ListView |
| 52 * elements. | 51 * elements. |
| 53 */ | 52 */ |
| 54 class _PlaceholderView extends View { | 53 class _PlaceholderView extends View { |
| 55 _PlaceholderView() : super() {} | 54 _PlaceholderView() : super() {} |
| 56 | 55 |
| 57 Element render() => new Element.tag('div'); | 56 Element render() => new Element.tag('div'); |
| 58 } | 57 } |
| 59 | 58 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 91 | 90 |
| 92 int getEstimatedLength(int viewLength); | 91 int getEstimatedLength(int viewLength); |
| 93 /** | 92 /** |
| 94 * Snap a specified index to the nearest visible view given the [viewLength]. | 93 * Snap a specified index to the nearest visible view given the [viewLength]. |
| 95 */ | 94 */ |
| 96 int getSnapIndex(num offset, num viewLength); | 95 int getSnapIndex(num offset, num viewLength); |
| 97 /** | 96 /** |
| 98 * Returns an interval specifying what views are currently visible given a | 97 * Returns an interval specifying what views are currently visible given a |
| 99 * particular [:offset:]. | 98 * particular [:offset:]. |
| 100 */ | 99 */ |
| 101 Interval computeVisibleInterval(num offset, num viewLength, | 100 Interval computeVisibleInterval(num offset, num viewLength, num bufferLength); |
| 102 num bufferLength); | |
| 103 } | 101 } |
| 104 | 102 |
| 105 /** | 103 /** |
| 106 * Base class used for the simple fixed size item [:ListView:] classes and more | 104 * Base class used for the simple fixed size item [:ListView:] classes and more |
| 107 * complex list view classes such as [:VariableSizeListView:] using a | 105 * complex list view classes such as [:VariableSizeListView:] using a |
| 108 * [:ListViewLayout:] class to drive the actual layout. | 106 * [:ListViewLayout:] class to drive the actual layout. |
| 109 */ | 107 */ |
| 110 class GenericListView<D> extends View { | 108 class GenericListView<D> extends View { |
| 111 /** Minimum throw distance in pixels to trigger snapping to the next item. */ | 109 /** Minimum throw distance in pixels to trigger snapping to the next item. */ |
| 112 static const SNAP_TO_NEXT_THROW_THRESHOLD = 15; | 110 static const SNAP_TO_NEXT_THROW_THRESHOLD = 15; |
| (...skipping 18 matching lines...) Expand all Loading... |
| 131 ListViewLayout<D> _layout; | 129 ListViewLayout<D> _layout; |
| 132 D _lastSelectedItem; | 130 D _lastSelectedItem; |
| 133 PageState _pages; | 131 PageState _pages; |
| 134 | 132 |
| 135 /** | 133 /** |
| 136 * Creates a new GenericListView with the given layout and data. If [:_data:] | 134 * Creates a new GenericListView with the given layout and data. If [:_data:] |
| 137 * is an [:ObservableList<T>:] then it will listen to changes to the list | 135 * is an [:ObservableList<T>:] then it will listen to changes to the list |
| 138 * and update the view appropriately. | 136 * and update the view appropriately. |
| 139 */ | 137 */ |
| 140 GenericListView( | 138 GenericListView( |
| 141 this._layout, | 139 this._layout, |
| 142 this._data, | 140 this._data, |
| 143 this._scrollable, | 141 this._scrollable, |
| 144 this._vertical, | 142 this._vertical, |
| 145 this._selectedItem, | 143 this._selectedItem, |
| 146 this._snapToItems, | 144 this._snapToItems, |
| 147 this._paginate, | 145 this._paginate, |
| 148 this._removeClippedViews, | 146 this._removeClippedViews, |
| 149 this._showScrollbar, | 147 this._showScrollbar, |
| 150 this._pages) | 148 this._pages) |
| 151 : super(), | 149 : super(), |
| 152 _activeInterval = new Interval(0, 0), | 150 _activeInterval = new Interval(0, 0), |
| 153 _itemViews = new Map<int, View>() { | 151 _itemViews = new Map<int, View>() { |
| 154 // TODO(rnystrom): Move this into enterDocument once we have an exitDocument | 152 // TODO(rnystrom): Move this into enterDocument once we have an exitDocument |
| 155 // that we can use to unregister it. | 153 // that we can use to unregister it. |
| 156 if (_scrollable) { | 154 if (_scrollable) { |
| 157 window.onResize.listen((Event event) { | 155 window.onResize.listen((Event event) { |
| 158 if (isInDocument) { | 156 if (isInDocument) { |
| 159 onResize(); | 157 onResize(); |
| 160 } | 158 } |
| 161 }); | 159 }); |
| 162 } | 160 } |
| 163 } | 161 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 209 node.nodes.add(_containerElem); | 207 node.nodes.add(_containerElem); |
| 210 } else { | 208 } else { |
| 211 _containerElem = node; | 209 _containerElem = node; |
| 212 } | 210 } |
| 213 | 211 |
| 214 if (_scrollable) { | 212 if (_scrollable) { |
| 215 scroller = new Scroller( | 213 scroller = new Scroller( |
| 216 _containerElem, | 214 _containerElem, |
| 217 _vertical /* verticalScrollEnabled */, | 215 _vertical /* verticalScrollEnabled */, |
| 218 !_vertical /* horizontalScrollEnabled */, | 216 !_vertical /* horizontalScrollEnabled */, |
| 219 true /* momentumEnabled */, | 217 true /* momentumEnabled */, () { |
| 220 () { | 218 num width = _layout.getWidth(_viewLength); |
| 221 num width = _layout.getWidth(_viewLength); | 219 num height = _layout.getHeight(_viewLength); |
| 222 num height = _layout.getHeight(_viewLength); | 220 width = width != null ? width : 0; |
| 223 width = width != null ? width : 0; | 221 height = height != null ? height : 0; |
| 224 height = height != null ? height : 0; | 222 return new Size(width, height); |
| 225 return new Size(width, height); | 223 }, |
| 226 }, | 224 _paginate && _snapToItems |
| 227 _paginate && _snapToItems ? | 225 ? Scroller.FAST_SNAP_DECELERATION_FACTOR |
| 228 Scroller.FAST_SNAP_DECELERATION_FACTOR : 1); | 226 : 1); |
| 229 scroller.onContentMoved.listen((e) => renderVisibleItems(false)); | 227 scroller.onContentMoved.listen((e) => renderVisibleItems(false)); |
| 230 if (_pages != null) { | 228 if (_pages != null) { |
| 231 watch(_pages.target, (s) => _onPageSelected()); | 229 watch(_pages.target, (s) => _onPageSelected()); |
| 232 } | 230 } |
| 233 | 231 |
| 234 if (_snapToItems) { | 232 if (_snapToItems) { |
| 235 scroller.onDecelStart.listen((e) => _decelStart()); | 233 scroller.onDecelStart.listen((e) => _decelStart()); |
| 236 scroller.onScrollerDragEnd.listen((e) => _decelStart()); | 234 scroller.onScrollerDragEnd.listen((e) => _decelStart()); |
| 237 } | 235 } |
| 238 if (_showScrollbar) { | 236 if (_showScrollbar) { |
| 239 _scrollbar = new Scrollbar(scroller, true); | 237 _scrollbar = new Scrollbar(scroller, true); |
| 240 } | 238 } |
| 241 } else { | 239 } else { |
| 242 _reserveArea(); | 240 _reserveArea(); |
| 243 renderVisibleItems(true); | 241 renderVisibleItems(true); |
| 244 } | 242 } |
| 245 | 243 |
| 246 return node; | 244 return node; |
| 247 } | 245 } |
| 248 | 246 |
| 249 void afterRender(Element node) { | 247 void afterRender(Element node) { |
| 250 // If our data source is observable, observe it. | 248 // If our data source is observable, observe it. |
| 251 if (_data is ObservableList<D>) { | 249 if (_data is ObservableList<D>) { |
| 252 ObservableList<D> observable = _data; | 250 ObservableList<D> observable = _data; |
| 253 attachWatch(observable, (EventSummary e) { | 251 attachWatch(observable, (EventSummary e) { |
| 254 if (e.target == observable) { | 252 if (e.target == observable) { |
| 255 onDataChange(); | 253 onDataChange(); |
| 256 } | 254 } |
| 257 }); | 255 }); |
| 258 } | 256 } |
| 259 | 257 |
| 260 if (_selectedItem != null) { | 258 if (_selectedItem != null) { |
| 261 addOnClick((Event e) { _onClick(e); }); | 259 addOnClick((Event e) { |
| 260 _onClick(e); |
| 261 }); |
| 262 } | 262 } |
| 263 | 263 |
| 264 if (_selectedItem != null) { | 264 if (_selectedItem != null) { |
| 265 watch(_selectedItem, (EventSummary summary) => onSelectedItemChange()); | 265 watch(_selectedItem, (EventSummary summary) => onSelectedItemChange()); |
| 266 } | 266 } |
| 267 } | 267 } |
| 268 | 268 |
| 269 void onDataChange() { | 269 void onDataChange() { |
| 270 _layout.onDataChange(); | 270 _layout.onDataChange(); |
| 271 _renderItems(); | 271 _renderItems(); |
| 272 } | 272 } |
| 273 | 273 |
| 274 void _reserveArea() { | 274 void _reserveArea() { |
| 275 final style = _containerElem.style; | 275 final style = _containerElem.style; |
| 276 int width = _layout.getWidth(_viewLength); | 276 int width = _layout.getWidth(_viewLength); |
| 277 int height = _layout.getHeight(_viewLength); | 277 int height = _layout.getHeight(_viewLength); |
| 278 if (width != null) { | 278 if (width != null) { |
| 279 style.width = '${width}px'; | 279 style.width = '${width}px'; |
| 280 } | 280 } |
| 281 if (height != null) { | 281 if (height != null) { |
| 282 style.height = '${height}px'; | 282 style.height = '${height}px'; |
| 283 } | 283 } |
| 284 // TODO(jacobr): this should be specified by the default CSS for a | 284 // TODO(jacobr): this should be specified by the default CSS for a |
| 285 // GenericListView. | 285 // GenericListView. |
| 286 style.overflow = 'hidden'; | 286 style.overflow = 'hidden'; |
| 287 } | 287 } |
| 288 | 288 |
| 289 | |
| 290 void onResize() { | 289 void onResize() { |
| 291 int lastViewLength = _viewLength; | 290 int lastViewLength = _viewLength; |
| 292 scheduleMicrotask(() { | 291 scheduleMicrotask(() { |
| 293 _viewLength = _vertical ? node.offset.height : node.offset.width; | 292 _viewLength = _vertical ? node.offset.height : node.offset.width; |
| 294 if (_viewLength != lastViewLength) { | 293 if (_viewLength != lastViewLength) { |
| 295 if (_scrollbar != null) { | 294 if (_scrollbar != null) { |
| 296 _scrollbar.refresh(); | 295 _scrollbar.refresh(); |
| 297 } | 296 } |
| 298 renderVisibleItems(true); | 297 renderVisibleItems(true); |
| 299 } | 298 } |
| (...skipping 15 matching lines...) Expand all Loading... |
| 315 if (_paginate) { | 314 if (_paginate) { |
| 316 int newPage = Math.max(0, _layout.getPage(index, _viewLength) + delta); | 315 int newPage = Math.max(0, _layout.getPage(index, _viewLength) + delta); |
| 317 index = _layout.getPageStartIndex(newPage, _viewLength); | 316 index = _layout.getPageStartIndex(newPage, _viewLength); |
| 318 } else { | 317 } else { |
| 319 index += delta; | 318 index += delta; |
| 320 } | 319 } |
| 321 return GoogleMath.clamp(index, 0, _data.length - 1); | 320 return GoogleMath.clamp(index, 0, _data.length - 1); |
| 322 } | 321 } |
| 323 | 322 |
| 324 void _decelStart() { | 323 void _decelStart() { |
| 325 num currentTarget = scroller.verticalEnabled ? | 324 num currentTarget = scroller.verticalEnabled |
| 326 scroller.currentTarget.y : scroller.currentTarget.x; | 325 ? scroller.currentTarget.y |
| 327 num current = scroller.verticalEnabled ? | 326 : scroller.currentTarget.x; |
| 328 scroller.contentOffset.y : scroller.contentOffset.x; | 327 num current = scroller.verticalEnabled |
| 328 ? scroller.contentOffset.y |
| 329 : scroller.contentOffset.x; |
| 329 num targetIndex = _layout.getSnapIndex(currentTarget, _viewLength); | 330 num targetIndex = _layout.getSnapIndex(currentTarget, _viewLength); |
| 330 if (current != currentTarget) { | 331 if (current != currentTarget) { |
| 331 // The user is throwing rather than statically releasing. | 332 // The user is throwing rather than statically releasing. |
| 332 // For this case, we want to move them to the next snap interval | 333 // For this case, we want to move them to the next snap interval |
| 333 // as long as they made at least a minimal throw gesture. | 334 // as long as they made at least a minimal throw gesture. |
| 334 num currentIndex = _layout.getSnapIndex(current, _viewLength); | 335 num currentIndex = _layout.getSnapIndex(current, _viewLength); |
| 335 if (currentIndex == targetIndex && | 336 if (currentIndex == targetIndex && |
| 336 (currentTarget - current).abs() > SNAP_TO_NEXT_THROW_THRESHOLD && | 337 (currentTarget - current).abs() > SNAP_TO_NEXT_THROW_THRESHOLD && |
| 337 -_layout.getOffset(targetIndex) != currentTarget) { | 338 -_layout.getOffset(targetIndex) != currentTarget) { |
| 338 num snappedCurrentPosition = -_layout.getOffset(targetIndex); | 339 num snappedCurrentPosition = -_layout.getOffset(targetIndex); |
| 339 targetIndex = getNextIndex(targetIndex, currentTarget < current); | 340 targetIndex = getNextIndex(targetIndex, currentTarget < current); |
| 340 } | 341 } |
| 341 } | 342 } |
| 342 num targetPosition = -_layout.getOffset(targetIndex); | 343 num targetPosition = -_layout.getOffset(targetIndex); |
| 343 if (currentTarget != targetPosition) { | 344 if (currentTarget != targetPosition) { |
| 344 if (scroller.verticalEnabled) { | 345 if (scroller.verticalEnabled) { |
| 345 scroller.throwTo(scroller.contentOffset.x, targetPosition); | 346 scroller.throwTo(scroller.contentOffset.x, targetPosition); |
| 346 } else { | 347 } else { |
| 347 scroller.throwTo(targetPosition, scroller.contentOffset.y); | 348 scroller.throwTo(targetPosition, scroller.contentOffset.y); |
| 348 } | 349 } |
| 349 } else { | 350 } else { |
| 350 // Update the target page only after we are all done animating. | 351 // Update the target page only after we are all done animating. |
| 351 if (_pages != null) { | 352 if (_pages != null) { |
| 352 _pages.target.value =_layout.getPage(targetIndex, _viewLength); | 353 _pages.target.value = _layout.getPage(targetIndex, _viewLength); |
| 353 } | 354 } |
| 354 } | 355 } |
| 355 } | 356 } |
| 356 | 357 |
| 357 void _renderItems() { | 358 void _renderItems() { |
| 358 for (int i = _activeInterval.start; i < _activeInterval.end; i++) { | 359 for (int i = _activeInterval.start; i < _activeInterval.end; i++) { |
| 359 _removeView(i); | 360 _removeView(i); |
| 360 } | 361 } |
| 361 _itemViews.clear(); | 362 _itemViews.clear(); |
| 362 _activeInterval = new Interval(0, 0); | 363 _activeInterval = new Interval(0, 0); |
| 363 if (scroller == null) { | 364 if (scroller == null) { |
| 364 _reserveArea(); | 365 _reserveArea(); |
| 365 } | 366 } |
| 366 renderVisibleItems(false); | 367 renderVisibleItems(false); |
| 367 } | 368 } |
| 368 | 369 |
| 369 void _onPageSelected() { | 370 void _onPageSelected() { |
| 370 if (_pages.target != | 371 if (_pages.target != _layout.getPage(_activeInterval.start, _viewLength)) { |
| 371 _layout.getPage(_activeInterval.start, _viewLength)) { | |
| 372 _throwTo(_layout.getOffset( | 372 _throwTo(_layout.getOffset( |
| 373 _layout.getPageStartIndex(_pages.target.value, _viewLength))); | 373 _layout.getPageStartIndex(_pages.target.value, _viewLength))); |
| 374 } | 374 } |
| 375 } | 375 } |
| 376 | 376 |
| 377 num get _offset { | 377 num get _offset { |
| 378 return scroller.verticalEnabled ? | 378 return scroller.verticalEnabled |
| 379 scroller.getVerticalOffset() : scroller.getHorizontalOffset(); | 379 ? scroller.getVerticalOffset() |
| 380 : scroller.getHorizontalOffset(); |
| 380 } | 381 } |
| 381 | 382 |
| 382 /** | 383 /** |
| 383 * Calculates visible interval, based on the scroller position. | 384 * Calculates visible interval, based on the scroller position. |
| 384 */ | 385 */ |
| 385 Interval getVisibleInterval() { | 386 Interval getVisibleInterval() { |
| 386 return _layout.computeVisibleInterval(_offset, _viewLength, 0); | 387 return _layout.computeVisibleInterval(_offset, _viewLength, 0); |
| 387 } | 388 } |
| 388 | 389 |
| 389 void renderVisibleItems(bool lengthChanged) { | 390 void renderVisibleItems(bool lengthChanged) { |
| 390 Interval targetInterval; | 391 Interval targetInterval; |
| 391 if (scroller != null) { | 392 if (scroller != null) { |
| 392 targetInterval = getVisibleInterval(); | 393 targetInterval = getVisibleInterval(); |
| 393 } else { | 394 } else { |
| 394 // If the view is not scrollable, render all elements. | 395 // If the view is not scrollable, render all elements. |
| 395 targetInterval = new Interval(0, _data.length); | 396 targetInterval = new Interval(0, _data.length); |
| 396 } | 397 } |
| 397 | 398 |
| 398 if (_pages != null) { | 399 if (_pages != null) { |
| 399 _pages.current.value = | 400 _pages.current.value = _layout.getPage(targetInterval.start, _viewLength); |
| 400 _layout.getPage(targetInterval.start, _viewLength); | |
| 401 } | 401 } |
| 402 if (_pages != null) { | 402 if (_pages != null) { |
| 403 _pages.length.value = _data.length > 0 ? | 403 _pages.length.value = _data.length > 0 |
| 404 _layout.getPage(_data.length - 1, _viewLength) + 1 : 0; | 404 ? _layout.getPage(_data.length - 1, _viewLength) + 1 |
| 405 : 0; |
| 405 } | 406 } |
| 406 | 407 |
| 407 if (!_removeClippedViews) { | 408 if (!_removeClippedViews) { |
| 408 // Avoid removing clipped views by extending the target interval to | 409 // Avoid removing clipped views by extending the target interval to |
| 409 // include the existing interval of rendered views. | 410 // include the existing interval of rendered views. |
| 410 targetInterval = targetInterval.union(_activeInterval); | 411 targetInterval = targetInterval.union(_activeInterval); |
| 411 } | 412 } |
| 412 | 413 |
| 413 if (lengthChanged == false && targetInterval == _activeInterval) { | 414 if (lengthChanged == false && targetInterval == _activeInterval) { |
| 414 return; | 415 return; |
| 415 } | 416 } |
| 416 | 417 |
| 417 // TODO(jacobr): add unittests that this code behaves correctly. | 418 // TODO(jacobr): add unittests that this code behaves correctly. |
| 418 | 419 |
| 419 // Remove views that are not needed anymore | 420 // Remove views that are not needed anymore |
| 420 for (int i = _activeInterval.start, | 421 for (int i = _activeInterval.start, |
| 421 end = Math.min(targetInterval.start, _activeInterval.end); | 422 end = Math.min(targetInterval.start, _activeInterval.end); |
| 422 i < end; i++) { | 423 i < end; |
| 424 i++) { |
| 423 _removeView(i); | 425 _removeView(i); |
| 424 } | 426 } |
| 425 for (int i = Math.max(targetInterval.end, _activeInterval.start); | 427 for (int i = Math.max(targetInterval.end, _activeInterval.start); |
| 426 i < _activeInterval.end; i++) { | 428 i < _activeInterval.end; |
| 429 i++) { |
| 427 _removeView(i); | 430 _removeView(i); |
| 428 } | 431 } |
| 429 | 432 |
| 430 // Add new views | 433 // Add new views |
| 431 for (int i = targetInterval.start, | 434 for (int i = targetInterval.start, |
| 432 end = Math.min(_activeInterval.start, targetInterval.end); | 435 end = Math.min(_activeInterval.start, targetInterval.end); |
| 433 i < end; i++) { | 436 i < end; |
| 437 i++) { |
| 434 _addView(i); | 438 _addView(i); |
| 435 } | 439 } |
| 436 for (int i = Math.max(_activeInterval.end, targetInterval.start); | 440 for (int i = Math.max(_activeInterval.end, targetInterval.start); |
| 437 i < targetInterval.end; i++) { | 441 i < targetInterval.end; |
| 442 i++) { |
| 438 _addView(i); | 443 _addView(i); |
| 439 } | 444 } |
| 440 | 445 |
| 441 _activeInterval = targetInterval; | 446 _activeInterval = targetInterval; |
| 442 } | 447 } |
| 443 | 448 |
| 444 void _removeView(int index) { | 449 void _removeView(int index) { |
| 445 // Do not remove placeholder views as they need to stay present in case | 450 // Do not remove placeholder views as they need to stay present in case |
| 446 // they scroll out of view and then back into view. | 451 // they scroll out of view and then back into view. |
| 447 if (!(_itemViews[index] is _PlaceholderView)) { | 452 if (!(_itemViews[index] is _PlaceholderView)) { |
| 448 // Remove from the active DOM but don't destroy. | 453 // Remove from the active DOM but don't destroy. |
| 449 _itemViews[index].node.remove(); | 454 _itemViews[index].node.remove(); |
| 450 childViewRemoved(_itemViews[index]); | 455 childViewRemoved(_itemViews[index]); |
| 451 } | 456 } |
| 452 } | 457 } |
| 453 | 458 |
| 454 View _newView(int index) { | 459 View _newView(int index) { |
| 455 final view = _layout.newView(index); | 460 final view = _layout.newView(index); |
| 456 view.node.attributes[INDEX_DATA_ATTRIBUTE] = index.toString(); | 461 view.node.attributes[INDEX_DATA_ATTRIBUTE] = index.toString(); |
| 457 return view; | 462 return view; |
| 458 } | 463 } |
| 459 | 464 |
| 460 View _addView(int index) { | 465 View _addView(int index) { |
| 461 if (_itemViews.containsKey(index)) { | 466 if (_itemViews.containsKey(index)) { |
| 462 final view = _itemViews[index]; | 467 final view = _itemViews[index]; |
| 463 _addViewHelper(view, index); | 468 _addViewHelper(view, index); |
| 464 childViewAdded(view); | 469 childViewAdded(view); |
| 465 return view; | 470 return view; |
| 466 } | 471 } |
| 467 | 472 |
| 468 final view = _newView(index); | 473 final view = _newView(index); |
| 469 _itemViews[index] = view; | 474 _itemViews[index] = view; |
| 470 // TODO(jacobr): its ugly to put this here... but its needed | 475 // TODO(jacobr): its ugly to put this here... but its needed |
| 471 // as typical even-odd css queries won't work as we only display some | 476 // as typical even-odd css queries won't work as we only display some |
| 472 // children at a time. | 477 // children at a time. |
| 473 if (index == 0) { | 478 if (index == 0) { |
| 474 view.addClass('first-child'); | 479 view.addClass('first-child'); |
| 475 } | 480 } |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 516 */ | 521 */ |
| 517 void reattachSubview(D data, View view, bool animate) { | 522 void reattachSubview(D data, View view, bool animate) { |
| 518 int index = findIndex(data); | 523 int index = findIndex(data); |
| 519 // TODO(jacobr): perform some validation that the view is | 524 // TODO(jacobr): perform some validation that the view is |
| 520 // really detached. | 525 // really detached. |
| 521 var currentPosition; | 526 var currentPosition; |
| 522 if (animate) { | 527 if (animate) { |
| 523 currentPosition = | 528 currentPosition = |
| 524 FxUtil.computeRelativePosition(view.node, _containerElem); | 529 FxUtil.computeRelativePosition(view.node, _containerElem); |
| 525 } | 530 } |
| 526 assert (_itemViews[index] is _PlaceholderView); | 531 assert(_itemViews[index] is _PlaceholderView); |
| 527 view.enterDocument(); | 532 view.enterDocument(); |
| 528 _itemViews[index].node.replaceWith(view.node); | 533 _itemViews[index].node.replaceWith(view.node); |
| 529 _itemViews[index] = view; | 534 _itemViews[index] = view; |
| 530 if (animate) { | 535 if (animate) { |
| 531 FxUtil.setTranslate(view.node, currentPosition.x, currentPosition.y, 0); | 536 FxUtil.setTranslate(view.node, currentPosition.x, currentPosition.y, 0); |
| 532 // The view's position is unchanged except now re-parented to | 537 // The view's position is unchanged except now re-parented to |
| 533 // the list view. | 538 // the list view. |
| 534 Timer.run(() { _positionSubview(view.node, index); }); | 539 Timer.run(() { |
| 540 _positionSubview(view.node, index); |
| 541 }); |
| 535 } else { | 542 } else { |
| 536 _positionSubview(view.node, index); | 543 _positionSubview(view.node, index); |
| 537 } | 544 } |
| 538 } | 545 } |
| 539 | 546 |
| 540 int findIndex(D targetItem) { | 547 int findIndex(D targetItem) { |
| 541 // TODO(jacobr): move this to a util library or modify this class so that | 548 // TODO(jacobr): move this to a util library or modify this class so that |
| 542 // the data is an List not a Collection. | 549 // the data is an List not a Collection. |
| 543 int i = 0; | 550 int i = 0; |
| 544 for (D item in _data) { | 551 for (D item in _data) { |
| 545 if (item == targetItem) { | 552 if (item == targetItem) { |
| 546 return i; | 553 return i; |
| 547 } | 554 } |
| 548 i++; | 555 i++; |
| 549 } | 556 } |
| 550 return null; | 557 return null; |
| 551 } | 558 } |
| 552 | 559 |
| 553 void _positionSubview(Element node, int index) { | 560 void _positionSubview(Element node, int index) { |
| 554 if (_vertical) { | 561 if (_vertical) { |
| 555 FxUtil.setTranslate(node, 0, _layout.getOffset(index), 0); | 562 FxUtil.setTranslate(node, 0, _layout.getOffset(index), 0); |
| 556 } else { | 563 } else { |
| 557 FxUtil.setTranslate(node, _layout.getOffset(index), 0, 0); | 564 FxUtil.setTranslate(node, _layout.getOffset(index), 0, 0); |
| 558 } | 565 } |
| 559 node.style.zIndex = index.toString(); | 566 node.style.zIndex = index.toString(); |
| 560 } | 567 } |
| 561 | 568 |
| 562 void _select(int index, bool selected) { | 569 void _select(int index, bool selected) { |
| 563 if (index != null) { | 570 if (index != null) { |
| 564 final subview = getSubview(index); | 571 final subview = getSubview(index); |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 602 } | 609 } |
| 603 } | 610 } |
| 604 } | 611 } |
| 605 | 612 |
| 606 class FixedSizeListViewLayout<D> implements ListViewLayout<D> { | 613 class FixedSizeListViewLayout<D> implements ListViewLayout<D> { |
| 607 final ViewFactory<D> itemViewFactory; | 614 final ViewFactory<D> itemViewFactory; |
| 608 final bool _vertical; | 615 final bool _vertical; |
| 609 List<D> _data; | 616 List<D> _data; |
| 610 bool _paginate; | 617 bool _paginate; |
| 611 | 618 |
| 612 FixedSizeListViewLayout(this.itemViewFactory, this._data, this._vertical, | 619 FixedSizeListViewLayout( |
| 613 this._paginate); | 620 this.itemViewFactory, this._data, this._vertical, this._paginate); |
| 614 | 621 |
| 615 void onDataChange() {} | 622 void onDataChange() {} |
| 616 | 623 |
| 617 View newView(int index) { | 624 View newView(int index) { |
| 618 return itemViewFactory.newView(_data[index]); | 625 return itemViewFactory.newView(_data[index]); |
| 619 } | 626 } |
| 620 | 627 |
| 621 int get _itemLength { | 628 int get _itemLength { |
| 622 return _vertical ? itemViewFactory.height : itemViewFactory.width; | 629 return _vertical ? itemViewFactory.height : itemViewFactory.width; |
| 623 } | 630 } |
| 624 | 631 |
| 625 | |
| 626 int getWidth(int viewLength) { | 632 int getWidth(int viewLength) { |
| 627 return _vertical ? itemViewFactory.width : getLength(viewLength); | 633 return _vertical ? itemViewFactory.width : getLength(viewLength); |
| 628 } | 634 } |
| 629 | 635 |
| 630 int getHeight(int viewLength) { | 636 int getHeight(int viewLength) { |
| 631 return _vertical ? getLength(viewLength) : itemViewFactory.height; | 637 return _vertical ? getLength(viewLength) : itemViewFactory.height; |
| 632 } | 638 } |
| 633 | 639 |
| 634 int getEstimatedHeight(int viewLength) { | 640 int getEstimatedHeight(int viewLength) { |
| 635 // Returns the exact height as it is trivial to compute for this layout. | 641 // Returns the exact height as it is trivial to compute for this layout. |
| 636 return getHeight(viewLength); | 642 return getHeight(viewLength); |
| 637 } | 643 } |
| 638 | 644 |
| 639 int getEstimatedWidth(int viewLength) { | 645 int getEstimatedWidth(int viewLength) { |
| 640 // Returns the exact height as it is trivial to compute for this layout. | 646 // Returns the exact height as it is trivial to compute for this layout. |
| 641 return getWidth(viewLength); | 647 return getWidth(viewLength); |
| 642 } | 648 } |
| 643 | 649 |
| 644 int getEstimatedLength(int viewLength) { | 650 int getEstimatedLength(int viewLength) { |
| 645 // Returns the exact length as it is trivial to compute for this layout. | 651 // Returns the exact length as it is trivial to compute for this layout. |
| 646 return getLength(viewLength); | 652 return getLength(viewLength); |
| 647 } | 653 } |
| 648 | 654 |
| 649 int getLength(int viewLength) { | 655 int getLength(int viewLength) { |
| 650 int itemLength = | 656 int itemLength = _vertical ? itemViewFactory.height : itemViewFactory.width; |
| 651 _vertical ? itemViewFactory.height : itemViewFactory.width; | |
| 652 if (viewLength == null || viewLength == 0) { | 657 if (viewLength == null || viewLength == 0) { |
| 653 return itemLength * _data.length; | 658 return itemLength * _data.length; |
| 654 } else if (_paginate) { | 659 } else if (_paginate) { |
| 655 if (_data.length > 0) { | 660 if (_data.length > 0) { |
| 656 final pageLength = getPageLength(viewLength); | 661 final pageLength = getPageLength(viewLength); |
| 657 return getPage(_data.length - 1, viewLength) | 662 return getPage(_data.length - 1, viewLength) * pageLength + |
| 658 * pageLength + Math.max(viewLength, pageLength); | 663 Math.max(viewLength, pageLength); |
| 659 } else { | 664 } else { |
| 660 return 0; | 665 return 0; |
| 661 } | 666 } |
| 662 } else { | 667 } else { |
| 663 return itemLength * (_data.length - 1) + Math.max(viewLength, itemLength); | 668 return itemLength * (_data.length - 1) + Math.max(viewLength, itemLength); |
| 664 } | 669 } |
| 665 } | 670 } |
| 666 | 671 |
| 667 int getOffset(int index) { | 672 int getOffset(int index) { |
| 668 return index * _itemLength; | 673 return index * _itemLength; |
| (...skipping 29 matching lines...) Expand all Loading... |
| 698 targetIntervalStart, | 703 targetIntervalStart, |
| 699 _data.length); | 704 _data.length); |
| 700 return new Interval(targetIntervalStart, targetIntervalEnd.toInt()); | 705 return new Interval(targetIntervalStart, targetIntervalEnd.toInt()); |
| 701 } | 706 } |
| 702 } | 707 } |
| 703 | 708 |
| 704 /** | 709 /** |
| 705 * Simple list view class where each item has fixed width and height. | 710 * Simple list view class where each item has fixed width and height. |
| 706 */ | 711 */ |
| 707 class ListView<D> extends GenericListView<D> { | 712 class ListView<D> extends GenericListView<D> { |
| 708 | |
| 709 /** | 713 /** |
| 710 * Creates a new ListView for the given data. If [:_data:] is an | 714 * Creates a new ListView for the given data. If [:_data:] is an |
| 711 * [:ObservableList<T>:] then it will listen to changes to the list and | 715 * [:ObservableList<T>:] then it will listen to changes to the list and |
| 712 * update the view appropriately. | 716 * update the view appropriately. |
| 713 */ | 717 */ |
| 714 ListView(List<D> data, ViewFactory<D> itemViewFactory, bool scrollable, | 718 ListView(List<D> data, ViewFactory<D> itemViewFactory, bool scrollable, |
| 715 bool vertical, ObservableValue<D> selectedItem, | 719 bool vertical, ObservableValue<D> selectedItem, |
| 716 [bool snapToItems = false, | 720 [bool snapToItems = false, |
| 717 bool paginate = false, | 721 bool paginate = false, |
| 718 bool removeClippedViews = false, | 722 bool removeClippedViews = false, |
| 719 bool showScrollbar = false, | 723 bool showScrollbar = false, |
| 720 PageState pages = null]) | 724 PageState pages = null]) |
| 721 : super(new FixedSizeListViewLayout<D>(itemViewFactory, data, vertical, | 725 : super( |
| 722 paginate), | 726 new FixedSizeListViewLayout<D>( |
| 723 data, scrollable, vertical, selectedItem, snapToItems, paginate, | 727 itemViewFactory, data, vertical, paginate), |
| 724 removeClippedViews, showScrollbar, pages); | 728 data, |
| 729 scrollable, |
| 730 vertical, |
| 731 selectedItem, |
| 732 snapToItems, |
| 733 paginate, |
| 734 removeClippedViews, |
| 735 showScrollbar, |
| 736 pages); |
| 725 } | 737 } |
| 726 | 738 |
| 727 /** | 739 /** |
| 728 * Layout where each item may have variable size along the axis the list view | 740 * Layout where each item may have variable size along the axis the list view |
| 729 * extends. | 741 * extends. |
| 730 */ | 742 */ |
| 731 class VariableSizeListViewLayout<D> implements ListViewLayout<D> { | 743 class VariableSizeListViewLayout<D> implements ListViewLayout<D> { |
| 732 List<D> _data; | 744 List<D> _data; |
| 733 List<int> _itemOffsets; | 745 List<int> _itemOffsets; |
| 734 List<int> _lengths; | 746 List<int> _lengths; |
| 735 int _lastOffset = 0; | 747 int _lastOffset = 0; |
| 736 bool _vertical; | 748 bool _vertical; |
| 737 bool _paginate; | 749 bool _paginate; |
| 738 VariableSizeViewFactory<D> itemViewFactory; | 750 VariableSizeViewFactory<D> itemViewFactory; |
| 739 Interval _lastVisibleInterval; | 751 Interval _lastVisibleInterval; |
| 740 | 752 |
| 741 VariableSizeListViewLayout(this.itemViewFactory, data, this._vertical, | 753 VariableSizeListViewLayout( |
| 742 this._paginate) : | 754 this.itemViewFactory, data, this._vertical, this._paginate) |
| 743 _data = data, | 755 : _data = data, |
| 744 _lastVisibleInterval = new Interval(0, 0) { | 756 _lastVisibleInterval = new Interval(0, 0) { |
| 745 _itemOffsets = <int>[]; | 757 _itemOffsets = <int>[]; |
| 746 _lengths = <int>[]; | 758 _lengths = <int>[]; |
| 747 _itemOffsets.add(0); | 759 _itemOffsets.add(0); |
| 748 } | 760 } |
| 749 | 761 |
| 750 void onDataChange() { | 762 void onDataChange() { |
| 751 _itemOffsets.clear(); | 763 _itemOffsets.clear(); |
| 752 _itemOffsets.add(0); | 764 _itemOffsets.add(0); |
| 753 _lengths.clear(); | 765 _lengths.clear(); |
| 754 } | 766 } |
| 755 | 767 |
| 756 View newView(int index) => itemViewFactory.newView(_data[index]); | 768 View newView(int index) => itemViewFactory.newView(_data[index]); |
| 757 | 769 |
| 758 int getWidth(int viewLength) { | 770 int getWidth(int viewLength) { |
| 759 if (_vertical) { | 771 if (_vertical) { |
| 760 return itemViewFactory.getWidth(null); | 772 return itemViewFactory.getWidth(null); |
| 761 } else { | 773 } else { |
| 762 return getLength(viewLength); | 774 return getLength(viewLength); |
| (...skipping 30 matching lines...) Expand all Loading... |
| 793 if (_lengths.length == _data.length) { | 805 if (_lengths.length == _data.length) { |
| 794 // No need to estimate... we have all the data already. | 806 // No need to estimate... we have all the data already. |
| 795 return getLength(viewLength); | 807 return getLength(viewLength); |
| 796 } | 808 } |
| 797 if (_itemOffsets.length > 1 && _lengths.length > 0) { | 809 if (_itemOffsets.length > 1 && _lengths.length > 0) { |
| 798 // Estimate length by taking the average of the lengths | 810 // Estimate length by taking the average of the lengths |
| 799 // of the known views. | 811 // of the known views. |
| 800 num lengthFromAllButLastElement = 0; | 812 num lengthFromAllButLastElement = 0; |
| 801 if (_itemOffsets.length > 2) { | 813 if (_itemOffsets.length > 2) { |
| 802 lengthFromAllButLastElement = | 814 lengthFromAllButLastElement = |
| 803 (getOffset(_itemOffsets.length - 2) - | 815 (getOffset(_itemOffsets.length - 2) - getOffset(0)) * |
| 804 getOffset(0)) * | 816 (_data.length / (_itemOffsets.length - 2)); |
| 805 (_data.length / (_itemOffsets.length - 2)); | |
| 806 } | 817 } |
| 807 return (lengthFromAllButLastElement + | 818 return (lengthFromAllButLastElement + |
| 808 Math.max(viewLength, _lengths[_lengths.length - 1])).toInt(); | 819 Math.max(viewLength, _lengths[_lengths.length - 1])) |
| 820 .toInt(); |
| 809 } else { | 821 } else { |
| 810 if (_lengths.length == 1) { | 822 if (_lengths.length == 1) { |
| 811 return Math.max(viewLength, _lengths[0]); | 823 return Math.max(viewLength, _lengths[0]); |
| 812 } else { | 824 } else { |
| 813 return viewLength; | 825 return viewLength; |
| 814 } | 826 } |
| 815 } | 827 } |
| 816 } | 828 } |
| 817 | 829 |
| 818 int getLength(int viewLength) { | 830 int getLength(int viewLength) { |
| 819 if (_data.length == 0) { | 831 if (_data.length == 0) { |
| 820 return viewLength; | 832 return viewLength; |
| 821 } else { | 833 } else { |
| 822 // Hack so that _lengths[length - 1] is available. | 834 // Hack so that _lengths[length - 1] is available. |
| 823 getOffset(_data.length); | 835 getOffset(_data.length); |
| 824 return (getOffset(_data.length - 1) - getOffset(0)) + | 836 return (getOffset(_data.length - 1) - getOffset(0)) + |
| 825 Math.max(_lengths[_lengths.length - 1], viewLength); | 837 Math.max(_lengths[_lengths.length - 1], viewLength); |
| 826 } | 838 } |
| 827 } | 839 } |
| 828 | 840 |
| 829 int getOffset(int index) { | 841 int getOffset(int index) { |
| 830 if (index >= _itemOffsets.length) { | 842 if (index >= _itemOffsets.length) { |
| 831 int offset = _itemOffsets[_itemOffsets.length - 1]; | 843 int offset = _itemOffsets[_itemOffsets.length - 1]; |
| 832 for (int i = _itemOffsets.length; i <= index; i++) { | 844 for (int i = _itemOffsets.length; i <= index; i++) { |
| 833 int length = _vertical ? itemViewFactory.getHeight(_data[i - 1]) | 845 int length = _vertical |
| 846 ? itemViewFactory.getHeight(_data[i - 1]) |
| 834 : itemViewFactory.getWidth(_data[i - 1]); | 847 : itemViewFactory.getWidth(_data[i - 1]); |
| 835 offset += length; | 848 offset += length; |
| 836 _itemOffsets.add(offset); | 849 _itemOffsets.add(offset); |
| 837 _lengths.add(length); | 850 _lengths.add(length); |
| 838 } | 851 } |
| 839 } | 852 } |
| 840 return _itemOffsets[index]; | 853 return _itemOffsets[index]; |
| 841 } | 854 } |
| 842 | 855 |
| 843 int getPage(int index, int viewLength) { | 856 int getPage(int index, int viewLength) { |
| 844 // TODO(jacobr): implement. | 857 // TODO(jacobr): implement. |
| 845 throw 'Not implemented'; | 858 throw 'Not implemented'; |
| 846 } | 859 } |
| 847 | 860 |
| 848 int getPageStartIndex(int page, int viewLength) { | 861 int getPageStartIndex(int page, int viewLength) { |
| 849 // TODO(jacobr): implement. | 862 // TODO(jacobr): implement. |
| 850 throw 'Not implemented'; | 863 throw 'Not implemented'; |
| 851 } | 864 } |
| 852 | 865 |
| 853 int getSnapIndex(num offset, int viewLength) { | 866 int getSnapIndex(num offset, int viewLength) { |
| 854 for (int i = 1; i < _data.length; i++) { | 867 for (int i = 1; i < _data.length; i++) { |
| 855 if (getOffset(i) + getOffset(i - 1) > -offset * 2) { | 868 if (getOffset(i) + getOffset(i - 1) > -offset * 2) { |
| 856 return i - 1; | 869 return i - 1; |
| 857 } | 870 } |
| 858 } | 871 } |
| 859 return _data.length - 1; | 872 return _data.length - 1; |
| 860 } | 873 } |
| 861 | 874 |
| 862 Interval computeVisibleInterval( | 875 Interval computeVisibleInterval( |
| 863 num offset, num viewLength, num bufferLength) { | 876 num offset, num viewLength, num bufferLength) { |
| 864 offset = offset.toInt(); | 877 offset = offset.toInt(); |
| 865 int start = _findFirstItemBefore( | 878 int start = _findFirstItemBefore(-offset - bufferLength, |
| 866 -offset - bufferLength, | |
| 867 _lastVisibleInterval != null ? _lastVisibleInterval.start : 0); | 879 _lastVisibleInterval != null ? _lastVisibleInterval.start : 0); |
| 868 int end = _findFirstItemAfter( | 880 int end = _findFirstItemAfter(-offset + viewLength + bufferLength, |
| 869 -offset + viewLength + bufferLength, | |
| 870 _lastVisibleInterval != null ? _lastVisibleInterval.end : 0); | 881 _lastVisibleInterval != null ? _lastVisibleInterval.end : 0); |
| 871 _lastVisibleInterval = new Interval(start, Math.max(start, end)); | 882 _lastVisibleInterval = new Interval(start, Math.max(start, end)); |
| 872 _lastOffset = offset; | 883 _lastOffset = offset; |
| 873 return _lastVisibleInterval; | 884 return _lastVisibleInterval; |
| 874 } | 885 } |
| 875 | 886 |
| 876 int _findFirstItemAfter(num target, int hint) { | 887 int _findFirstItemAfter(num target, int hint) { |
| 877 for (int i = 0; i < _data.length; i++) { | 888 for (int i = 0; i < _data.length; i++) { |
| 878 if (getOffset(i) > target) { | 889 if (getOffset(i) > target) { |
| 879 return i; | 890 return i; |
| 880 } | 891 } |
| 881 } | 892 } |
| 882 return _data.length; | 893 return _data.length; |
| 883 } | 894 } |
| 884 | 895 |
| 885 // TODO(jacobr): use hint. | 896 // TODO(jacobr): use hint. |
| 886 int _findFirstItemBefore(num target, int hint) { | 897 int _findFirstItemBefore(num target, int hint) { |
| 887 // We go search this direction delaying computing the actual view size | 898 // We go search this direction delaying computing the actual view size |
| 888 // as long as possible. | 899 // as long as possible. |
| 889 for (int i = 1; i < _data.length; i++) { | 900 for (int i = 1; i < _data.length; i++) { |
| 890 if (getOffset(i) >= target) { | 901 if (getOffset(i) >= target) { |
| 891 return i - 1; | 902 return i - 1; |
| 892 } | 903 } |
| 893 } | 904 } |
| 894 return Math.max(_data.length - 1, 0); | 905 return Math.max(_data.length - 1, 0); |
| 895 } | 906 } |
| 896 } | 907 } |
| 897 | 908 |
| 898 class VariableSizeListView<D> extends GenericListView<D> { | 909 class VariableSizeListView<D> extends GenericListView<D> { |
| 899 | 910 VariableSizeListView(List<D> data, VariableSizeViewFactory<D> itemViewFactory, |
| 900 VariableSizeListView(List<D> data, | 911 bool scrollable, bool vertical, ObservableValue<D> selectedItem, |
| 901 VariableSizeViewFactory<D> itemViewFactory, | 912 [bool snapToItems = false, |
| 902 bool scrollable, | 913 bool paginate = false, |
| 903 bool vertical, | 914 bool removeClippedViews = false, |
| 904 ObservableValue<D> selectedItem, | 915 bool showScrollbar = false, |
| 905 [bool snapToItems = false, | 916 PageState pages = null]) |
| 906 bool paginate = false, | 917 : super( |
| 907 bool removeClippedViews = false, | 918 new VariableSizeListViewLayout( |
| 908 bool showScrollbar = false, | 919 itemViewFactory, data, vertical, paginate), |
| 909 PageState pages = null]) | 920 data, |
| 910 : super(new VariableSizeListViewLayout(itemViewFactory, data, vertical, | 921 scrollable, |
| 911 paginate), | 922 vertical, |
| 912 data, scrollable, vertical, selectedItem, snapToItems, | 923 selectedItem, |
| 913 paginate, removeClippedViews, showScrollbar, pages); | 924 snapToItems, |
| 925 paginate, |
| 926 removeClippedViews, |
| 927 showScrollbar, |
| 928 pages); |
| 914 } | 929 } |
| 915 | 930 |
| 916 /** A back button that is equivalent to clicking "back" in the browser. */ | 931 /** A back button that is equivalent to clicking "back" in the browser. */ |
| 917 class BackButton extends View { | 932 class BackButton extends View { |
| 918 BackButton() : super(); | 933 BackButton() : super(); |
| 919 | 934 |
| 920 Element render() => new Element.html('<div class="back-arrow button"></div>'); | 935 Element render() => new Element.html('<div class="back-arrow button"></div>'); |
| 921 | 936 |
| 922 void afterRender(Element node) { | 937 void afterRender(Element node) { |
| 923 addOnClick((e) => window.history.back()); | 938 addOnClick((e) => window.history.back()); |
| 924 } | 939 } |
| 925 } | 940 } |
| 926 | 941 |
| 927 | |
| 928 // TODO(terry): Maybe should be part of ButtonView class in appstack/view? | 942 // TODO(terry): Maybe should be part of ButtonView class in appstack/view? |
| 929 /** OS button. */ | 943 /** OS button. */ |
| 930 class PushButtonView extends View { | 944 class PushButtonView extends View { |
| 931 final String _text; | 945 final String _text; |
| 932 final String _cssClass; | 946 final String _cssClass; |
| 933 final _clickHandler; | 947 final _clickHandler; |
| 934 | 948 |
| 935 PushButtonView(this._text, this._cssClass, this._clickHandler) : super(); | 949 PushButtonView(this._text, this._cssClass, this._clickHandler) : super(); |
| 936 | 950 |
| 937 Element render() { | 951 Element render() { |
| 938 return new Element.html('<button class="${_cssClass}">${_text}</button>'); | 952 return new Element.html('<button class="${_cssClass}">${_text}</button>'); |
| 939 } | 953 } |
| 940 | 954 |
| 941 void afterRender(Element node) { | 955 void afterRender(Element node) { |
| 942 addOnClick(_clickHandler); | 956 addOnClick(_clickHandler); |
| 943 } | 957 } |
| 944 } | 958 } |
| 945 | 959 |
| 946 | |
| 947 // TODO(terry): Add a drop shadow around edge and corners need to be rounded. | 960 // TODO(terry): Add a drop shadow around edge and corners need to be rounded. |
| 948 // Need to support conveyor for contents of dialog so it's not | 961 // Need to support conveyor for contents of dialog so it's not |
| 949 // larger than the parent window. | 962 // larger than the parent window. |
| 950 /** A generic dialog view supports title, done button and dialog content. */ | 963 /** A generic dialog view supports title, done button and dialog content. */ |
| 951 class DialogView extends View { | 964 class DialogView extends View { |
| 952 final String _title; | 965 final String _title; |
| 953 final String _cssName; | 966 final String _cssName; |
| 954 final View _content; | 967 final View _content; |
| 955 Element container; | 968 Element container; |
| 956 PushButtonView _done; | 969 PushButtonView _done; |
| 957 | 970 |
| 958 DialogView(this._title, this._cssName, this._content) : super() {} | 971 DialogView(this._title, this._cssName, this._content) : super() {} |
| 959 | 972 |
| 960 Element render() { | 973 Element render() { |
| 961 final node = new Element.html(''' | 974 final node = new Element.html(''' |
| 962 <div class="dialog-modal"> | 975 <div class="dialog-modal"> |
| 963 <div class="dialog $_cssName"> | 976 <div class="dialog $_cssName"> |
| 964 <div class="dialog-title-area"> | 977 <div class="dialog-title-area"> |
| 965 <span class="dialog-title">$_title</span> | 978 <span class="dialog-title">$_title</span> |
| 966 </div> | 979 </div> |
| 967 <div class="dialog-body"></div> | 980 <div class="dialog-body"></div> |
| 968 </div> | 981 </div> |
| 969 </div>'''); | 982 </div>'''); |
| 970 | 983 |
| 971 _done = new PushButtonView('Done', 'done-button', | 984 _done = new PushButtonView( |
| 972 EventBatch.wrap((e) => onDone())); | 985 'Done', 'done-button', EventBatch.wrap((e) => onDone())); |
| 973 final titleArea = node.querySelector('.dialog-title-area'); | 986 final titleArea = node.querySelector('.dialog-title-area'); |
| 974 titleArea.nodes.add(_done.node); | 987 titleArea.nodes.add(_done.node); |
| 975 | 988 |
| 976 container = node.querySelector('.dialog-body'); | 989 container = node.querySelector('.dialog-body'); |
| 977 container.nodes.add(_content.node); | 990 container.nodes.add(_content.node); |
| 978 | 991 |
| 979 return node; | 992 return node; |
| 980 } | 993 } |
| 981 | 994 |
| 982 /** Override to handle dialog done. */ | 995 /** Override to handle dialog done. */ |
| 983 void onDone() { } | 996 void onDone() {} |
| 984 } | 997 } |
| OLD | NEW |