// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // @dart = 2.9 part of view; typedef void SelectHandler(String menuText); /** * This implements a horizontal menu bar with a sliding triangle arrow * that points at the currently selected item. */ class SliderMenu extends View { static const int TRIANGLE_WIDTH = 24; // currently selected menu item Element selectedItem; // This holds the element where a touchstart occured. (This is set // in touchstart, and cleared in touchend.) If this is null, then a // touch operation is not in progress. // TODO(mattsh) - move this to a touch mixin Element touchItem; /** * Callback function that we call when the user chooses something from * the menu. This is passed the menu item text. */ SelectHandler onSelect; List _menuItems; SliderMenu(this._menuItems, this.onSelect); Element render() { // Create a div for each menu item. final items = new StringBuffer(); for (final item in _menuItems) { items.write('
$item
'); } // Create a root node to hold this view. return new Element.html('''
$items
'''); } void enterDocument() { // select the first item // todo(jacobr): too much actual work is performed in enterDocument. // Ideally, enterDocument should do nothing more than redecorate a view // and perhaps calculating the correct child sizes for edge cases that // cannot be handled by the browser layout engine. selectItem(node.querySelector('.sm-item'), false); // TODO(mattsh), abstract this somehow into a touch click mixin if (Device.supportsTouch) { node.onTouchStart.listen((event) { touchItem = itemOfTouchEvent(event); if (touchItem != null) { selectItemText(touchItem); } event.preventDefault(); }); node.onTouchEnd.listen((event) { if (touchItem != null) { if (itemOfTouchEvent(event) == touchItem) { selectItem(touchItem, true); } else { // the Touch target is somewhere other where than the touchstart // occured, so revert the selected menu text back to where it was // before the touchstart, selectItemText(selectedItem); } // touch operation has ended touchItem = null; } event.preventDefault(); }); } else { node.onClick.listen((event) => selectItem(event.target, true)); } window.onResize.listen((Event event) => updateIndicator(false)); } /** * Walks the parent chain of the first Touch target to find the first ancestor * that has sm-item class. */ Element itemOfTouchEvent(event) { Node node = event.changedTouches[0].target; return itemOfNode(node); } Element itemOfNode(Node node) { // TODO(jmesserly): workaround for bug 5399957, document.parent == document while (node != null && node != document) { if (node is Element) { Element element = node; if (element.classes.contains('sm-item')) { return element; } } node = node.parent; } return null; } void selectItemText(Element item) { // unselect all menu items for (final sliderItem in node.querySelectorAll('.sm-item')) { sliderItem.classes.remove('sel'); } // select the item the user clicked on item.classes.add('sel'); } void selectItem(Element item, bool animate) { if (!item.classes.contains('sm-item')) { return; } selectedItem = item; selectItemText(item); updateIndicator(animate); onSelect(item.text); } void selectNext(bool animate) { final result = node.querySelector('.sm-item.sel').nextElementSibling; if (result != null) { selectItem(result, animate); } } void selectPrevious(bool animate) { final result = node.querySelector('.sm-item.sel').previousElementSibling; if (result != null) { selectItem(result, animate); } } /** * animate - if true, then animate the movement of the triangle slider */ void updateIndicator(bool animate) { if (selectedItem != null) { // calculate where we want to put the triangle scheduleMicrotask(() { num x = selectedItem.offset.left + selectedItem.offset.width / 2 - TRIANGLE_WIDTH / 2; _moveIndicator(x, animate); }); } else { _moveIndicator(0, animate); } } void _moveIndicator(num x, bool animate) { // find the slider filler (the div element to the left of the // triangle) set its width the push the triangle to where we want it. String duration = animate ? '.3s' : '0s'; final triangle = node.querySelector('.sm-triangle'); triangle.style.transitionDuration = duration; FxUtil.setWebkitTransform(triangle, x, 0); } }