UNPKG

iuap-design

Version:
1,382 lines (1,260 loc) 467 kB
'use strict'; var BaseComponent = u.Class.create({ initialize: function initialize(element) { if (u.isDomElement(element)) { this.element = element; this.options = {}; } else { this.element = element['el']; this.options = element; } this.element = typeof this.element === 'string' ? document.querySelector(this.element) : this.element; this.compType = this.compType || this.constructor.compType; this.element[this.compType] = this; this.element['init'] = true; this.init(); }, /** * 绑定事件 * @param {String} name * @param {Function} callback */ on: function on(name, callback) { name = name.toLowerCase(); this._events || (this._events = {}); var events = this._events[name] || (this._events[name] = []); events.push({ callback: callback }); return this; }, /** * 触发事件 * @param {String} name */ trigger: function trigger(name) { name = name.toLowerCase(); if (!this._events || !this._events[name]) return this; var args = Array.prototype.slice.call(arguments, 1); var events = this._events[name]; for (var i = 0, count = events.length; i < count; i++) { events[i].callback.apply(this, args); } return this; }, /** * 初始化 */ init: function init() {}, /** * 渲染控件 */ render: function render() {}, /** * 销毁控件 */ destroy: function destroy() { delete this.element['comp']; this.element.innerHTML = ''; }, /** * 增加dom事件 * @param {String} name * @param {Function} callback */ addDomEvent: function addDomEvent(name, callback) { u.on(this.element, name, callback); return this; }, /** * 移除dom事件 * @param {String} name */ removeDomEvent: function removeDomEvent(name, callback) { u.off(this.element, name, callback); return this; }, setEnable: function setEnable(enable) { return this; }, /** * 判断是否为DOM事件 */ isDomEvent: function isDomEvent(eventName) { if (this.element['on' + eventName] === undefined) return false;else return true; }, createDateAdapter: function createDateAdapter(options) { var opt = options['options'], model = options['model']; var Adapter = u.compMgr.getDataAdapter(this.compType, opt['dataType']); if (Adapter) { this.dataAdapter = new Adapter(this, options); } }, Statics: { compName: '', EVENT_VALUE_CHANGE: 'valueChange', getName: function getName() { return this.compName; } } }); function adjustDataType(options) { var types = ['integer', 'float', 'currency', 'percent', 'string', 'textarea']; var _type = options['type'], _dataType = options['dataType']; if (types.indexOf(_type) != -1) { options['dataType'] = _type; options['type'] = 'originText'; } } u.BaseComponent = BaseComponent; 'use strict'; var URipple = function URipple(element) { if (u.isIE8) return; this._element = element; // Initialize instance. this.init(); }; //window['URipple'] = URipple; URipple.prototype._down = function (event) { if (u.isIE8) return; if (!this._rippleElement.style.width && !this._rippleElement.style.height) { var rect = this._element.getBoundingClientRect(); this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2; this._rippleElement.style.width = this.rippleSize_ + 'px'; this._rippleElement.style.height = this.rippleSize_ + 'px'; } u.addClass(this._rippleElement, 'is-visible'); if (event.type === 'mousedown' && this._ignoringMouseDown) { this._ignoringMouseDown = false; } else { if (event.type === 'touchstart') { this._ignoringMouseDown = true; } var frameCount = this.getFrameCount(); if (frameCount > 0) { return; } this.setFrameCount(1); var t = event.currentTarget || event.target || event.srcElement; var bound = t.getBoundingClientRect(); var x; var y; // Check if we are handling a keyboard click. if (event.clientX === 0 && event.clientY === 0) { x = Math.round(bound.width / 2); y = Math.round(bound.height / 2); } else { var clientX = event.clientX ? event.clientX : event.touches[0].clientX; var clientY = event.clientY ? event.clientY : event.touches[0].clientY; x = Math.round(clientX - bound.left); y = Math.round(clientY - bound.top); } this.setRippleXY(x, y); this.setRippleStyles(true); if (window.requestAnimationFrame) window.requestAnimationFrame(this.animFrameHandler.bind(this)); } }; /** * Handle mouse / finger up on element. * * @param {Event} event The event that fired. * @private */ URipple.prototype._up = function (event) { if (u.isIE8) return; var self = this; // Don't fire for the artificial "mouseup" generated by a double-click. if (event && event.detail !== 2) { u.removeClass(this._rippleElement, 'is-visible'); } // Allow a repaint to occur before removing this class, so the animation // shows for tap events, which seem to trigger a mouseup too soon after // mousedown. window.setTimeout(function () { u.removeClass(self._rippleElement, 'is-visible'); }, 0); }; /** * Getter for frameCount_. * @return {number} the frame count. */ URipple.prototype.getFrameCount = function () { if (u.isIE8) return; return this.frameCount_; }; /** * Setter for frameCount_. * @param {number} fC the frame count. */ URipple.prototype.setFrameCount = function (fC) { if (u.isIE8) return; this.frameCount_ = fC; }; /** * Getter for _rippleElement. * @return {Element} the ripple element. */ URipple.prototype.getRippleElement = function () { if (u.isIE8) return; return this._rippleElement; }; /** * Sets the ripple X and Y coordinates. * @param {number} newX the new X coordinate * @param {number} newY the new Y coordinate */ URipple.prototype.setRippleXY = function (newX, newY) { if (u.isIE8) return; this.x_ = newX; this.y_ = newY; }; /** * Sets the ripple styles. * @param {boolean} start whether or not this is the start frame. */ URipple.prototype.setRippleStyles = function (start) { if (u.isIE8) return; if (this._rippleElement !== null) { var transformString; var scale; var size; var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)'; if (start) { scale = 'scale(0.0001, 0.0001)'; size = '1px'; } else { scale = ''; size = this.rippleSize_ + 'px'; } transformString = 'translate(-50%, -50%) ' + offset + scale; this._rippleElement.style.webkitTransform = transformString; this._rippleElement.style.msTransform = transformString; this._rippleElement.style.transform = transformString; if (start) { u.removeClass(this._rippleElement, 'is-animating'); } else { u.addClass(this._rippleElement, 'is-animating'); } } }; /** * Handles an animation frame. */ URipple.prototype.animFrameHandler = function () { if (u.isIE8) return; if (this.frameCount_-- > 0) { window.requestAnimationFrame(this.animFrameHandler.bind(this)); } else { this.setRippleStyles(false); } }; /** * Initialize element. */ URipple.prototype.init = function () { if (u.isIE8) return; var self = this; if (this._element) { this._rippleElement = this._element.querySelector('.u-ripple'); if (!this._rippleElement) { this._rippleElement = document.createElement('span'); u.addClass(this._rippleElement, 'u-ripple'); this._element.appendChild(this._rippleElement); this._element.style.overflow = 'hidden'; this._element.style.position = 'relative'; } this.frameCount_ = 0; this.rippleSize_ = 0; this.x_ = 0; this.y_ = 0; // Touch start produces a compat mouse down event, which would cause a // second ripples. To avoid that, we use this property to ignore the first // mouse down after a touch start. this._ignoringMouseDown = false; u.on(this._element, 'mousedown', function (e) { self._down(e); }); u.on(this._element, 'touchstart', function (e) { self._down(e); }); u.on(this._element, 'mouseup', function (e) { self._up(e); }); u.on(this._element, 'mouseleave', function (e) { self._up(e); }); u.on(this._element, 'touchend', function (e) { self._up(e); }); u.on(this._element, 'blur', function (e) { self._up(e); }); } }; u.Ripple = URipple; 'use strict'; u.Button = u.BaseComponent.extend({ init: function init() { var rippleContainer = document.createElement('span'); u.addClass(rippleContainer, 'u-button-container'); this._rippleElement = document.createElement('span'); u.addClass(this._rippleElement, 'u-ripple'); if (u.isIE8) u.addClass(this._rippleElement, 'oldIE'); rippleContainer.appendChild(this._rippleElement); u.on(this._rippleElement, 'mouseup', this.element.blur); this.element.appendChild(rippleContainer); u.on(this.element, 'mouseup', this.element.blur); u.on(this.element, 'mouseleave', this.element.blur); this.ripple = new u.Ripple(this.element); } }); u.compMgr.regComp({ comp: u.Button, compAsString: 'u.Button', css: 'u-button' }); 'use strict'; u.NavLayout = u.BaseComponent.extend({ _Constant: { MAX_WIDTH: '(max-width: 1024px)', TAB_SCROLL_PIXELS: 100, MENU_ICON: 'menu', CHEVRON_LEFT: 'chevron_left', CHEVRON_RIGHT: 'chevron_right' }, /** * Modes. * * @enum {number} * @private */ _Mode: { STANDARD: 0, SEAMED: 1, WATERFALL: 2, SCROLL: 3 }, /** * Store strings for class names defined by this component that are used in * JavaScript. This allows us to simply change it in one place should we * decide to modify at a later date. * * @enum {string} * @private */ _CssClasses: { CONTAINER: 'u-navlayout-container', HEADER: 'u-navlayout-header', DRAWER: 'u-navlayout-drawer', CONTENT: 'u-navlayout-content', DRAWER_BTN: 'u-navlayout-drawer-button', ICON: 'fa', //JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', //RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container', //RIPPLE: 'mdl-ripple', //RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', HEADER_SEAMED: 'seamed', HEADER_WATERFALL: 'waterfall', HEADER_SCROLL: 'scroll', FIXED_HEADER: 'fixed', OBFUSCATOR: 'u-navlayout-obfuscator', TAB_BAR: 'u-navlayout-tab-bar', TAB_CONTAINER: 'u-navlayout-tab-bar-container', TAB: 'u-navlayout-tab', TAB_BAR_BUTTON: 'u-navlayout-tab-bar-button', TAB_BAR_LEFT_BUTTON: 'u-navlayout-tab-bar-left-button', TAB_BAR_RIGHT_BUTTON: 'u-navlayout-tab-bar-right-button', PANEL: 'u-navlayout-tab-panel', HAS_DRAWER: 'has-drawer', HAS_TABS: 'has-tabs', HAS_SCROLLING_HEADER: 'has-scrolling-header', CASTING_SHADOW: 'is-casting-shadow', IS_COMPACT: 'is-compact', IS_SMALL_SCREEN: 'is-small-screen', IS_DRAWER_OPEN: 'is-visible', IS_ACTIVE: 'is-active', IS_UPGRADED: 'is-upgraded', IS_ANIMATING: 'is-animating', ON_LARGE_SCREEN: 'u-navlayout-large-screen-only', ON_SMALL_SCREEN: 'u-navlayout-small-screen-only', NAV: 'u-nav', NAV_LINK: 'u-nav-link', NAV_LINK_CURRENT: 'u-nav-link-current', NAV_LINK_OPEN: 'u-nav-link-open', NAV_SUB: 'u-nav-sub' }, init: function init() { var container = document.createElement('div'); u.addClass(container, this._CssClasses.CONTAINER); this.element.parentElement.insertBefore(container, this.element); this.element.parentElement.removeChild(this.element); container.appendChild(this.element); var directChildren = this.element.childNodes; var numChildren = directChildren.length; for (var c = 0; c < numChildren; c++) { var child = directChildren[c]; if (u.hasClass(child, this._CssClasses.HEADER)) { this._header = child; } if (u.hasClass(child, this._CssClasses.DRAWER)) { this._drawer = child; } if (u.hasClass(child, this._CssClasses.CONTENT)) { this._content = child; var layoutHeight = this.element.offsetHeight; var headerHeight = typeof this._header === 'undefined' ? 0 : this._header.offsetHeight; this._content.style.height = layoutHeight - headerHeight + 'px'; var self = this; u.on(window, 'resize', function () { var layoutHeight = self.element.offsetHeight; var headerHeight = typeof self._header === 'undefined' ? 0 : self._header.offsetHeight; self._content.style.height = layoutHeight - headerHeight + 'px'; }); } } if (this._header) { this._tabBar = this._header.querySelector('.' + this._CssClasses.TAB_BAR); } var mode = this._Mode.STANDARD; if (this._header) { if (u.hasClass(this._header, this._CssClasses.HEADER_SEAMED)) { mode = this._Mode.SEAMED; //} else if (u.hasClass(this._header,this._CssClasses.HEADER_SEAMED)) { // mode = this._Mode.WATERFALL; // u.on(this._header,'transitionend', this._headerTransitionEndHandler.bind(this)); // // this._header.addEventListener('transitionend', this._headerTransitionEndHandler.bind(this)); // u.on(this._header,'click', this._headerClickHandler.bind(this)); // // this._header.addEventListener('click', this._headerClickHandler.bind(this)); } else if (u.hasClass(this._header, this._CssClasses.HEADER_SCROLL)) { mode = this._Mode.SCROLL; u.addClass(container, this._CssClasses.HAS_SCROLLING_HEADER); } if (mode === this._Mode.STANDARD) { u.addClass(this._header, this._CssClasses.CASTING_SHADOW); if (this._tabBar) { u.addClass(this._tabBar, this._CssClasses.CASTING_SHADOW); } } else if (mode === this._Mode.SEAMED || mode === this._Mode.SCROLL) { u.removeClass(this._header, this._CssClasses.CASTING_SHADOW); if (this._tabBar) { u.removeClass(this._tabBar, this._CssClasses.CASTING_SHADOW); } } else if (mode === this._Mode.WATERFALL) { // Add and remove shadows depending on scroll position. // Also add/remove auxiliary class for styling of the compact version of // the header. u.on(this._content, 'scroll', this._contentScrollHandler.bind(this)); this._contentScrollHandler(); } } // Add drawer toggling button to our layout, if we have an openable drawer. if (this._drawer) { var drawerButton = this.element.querySelector('.' + this._CssClasses.DRAWER_BTN); if (!drawerButton) { drawerButton = document.createElement('div'); u.addClass(drawerButton, this._CssClasses.DRAWER_BTN); var drawerButtonIcon = document.createElement('i'); drawerButtonIcon.className = 'fa fa-bars'; //drawerButtonIcon.textContent = this._Constant.MENU_ICON; drawerButton.appendChild(drawerButtonIcon); } if (u.hasClass(this._drawer, this._CssClasses.ON_LARGE_SCREEN)) { //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well. u.addClass(drawerButton, this._CssClasses.ON_LARGE_SCREEN); } else if (u.hasClass(this._drawer, this._CssClasses.ON_SMALL_SCREEN)) { //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well. u.addClass(drawerButton, this._CssClasses.ON_SMALL_SCREEN); } u.on(drawerButton, 'click', this._drawerToggleHandler.bind(this)); // Add a class if the layout has a drawer, for altering the left padding. // Adds the HAS_DRAWER to the elements since this._header may or may // not be present. u.addClass(this.element, this._CssClasses.HAS_DRAWER); // If we have a fixed header, add the button to the header rather than // the layout. if (u.hasClass(this.element, this._CssClasses.FIXED_HEADER) && this._header) { this._header.insertBefore(drawerButton, this._header.firstChild); } else { this.element.insertBefore(drawerButton, this._content); } this.drawerButton = drawerButton; var obfuscator = document.createElement('div'); u.addClass(obfuscator, this._CssClasses.OBFUSCATOR); this.element.appendChild(obfuscator); u.on(obfuscator, 'click', this._drawerToggleHandler.bind(this)); this._obfuscator = obfuscator; var leftnav = this.element.querySelector('.' + this._CssClasses.NAV); u.on(leftnav, 'click', this._navlinkClickHander.bind(this)); var items = leftnav.querySelectorAll('.' + this._CssClasses.NAV_LINK); for (var i = 0; i < items.length; i++) { new u.Ripple(items[i]); } } // Keep an eye on screen size, and add/remove auxiliary class for styling // of small screens. if (u.isIE8 || u.isIE9) { u.on(window, 'resize', this._screenSizeHandler.bind(this)); } else { this._screenSizeMediaQuery = window.matchMedia( /** @type {string} */this._Constant.MAX_WIDTH); this._screenSizeMediaQuery.addListener(this._screenSizeHandler.bind(this)); } this._screenSizeHandler(); // Initialize tabs, if any. if (this._header && this._tabBar) { u.addClass(this.element, this._CssClasses.HAS_TABS); var tabContainer = document.createElement('div'); u.addClass(tabContainer, this._CssClasses.TAB_CONTAINER); this._header.insertBefore(tabContainer, this._tabBar); this._header.removeChild(this._tabBar); var leftButton = document.createElement('div'); u.addClass(leftButton, this._CssClasses.TAB_BAR_BUTTON); u.addClass(leftButton, this._CssClasses.TAB_BAR_LEFT_BUTTON); var leftButtonIcon = document.createElement('i'); u.addClass(leftButtonIcon, this._CssClasses.ICON); leftButtonIcon.textContent = this._Constant.CHEVRON_LEFT; leftButton.appendChild(leftButtonIcon); u.on(leftButton, 'click', function () { this._tabBar.scrollLeft -= this._Constant.TAB_SCROLL_PIXELS; }.bind(this)); var rightButton = document.createElement('div'); u.addClass(rightButton, this._CssClasses.TAB_BAR_BUTTON); u.addClass(rightButton, this._CssClasses.TAB_BAR_RIGHT_BUTTON); var rightButtonIcon = document.createElement('i'); u.addClass(rightButtonIcon, this._CssClasses.ICON); rightButtonIcon.textContent = this._Constant.CHEVRON_RIGHT; rightButton.appendChild(rightButtonIcon); u.on(rightButton, 'click', function () { this._tabBar.scrollLeft += this._Constant.TAB_SCROLL_PIXELS; }.bind(this)); tabContainer.appendChild(leftButton); tabContainer.appendChild(this._tabBar); tabContainer.appendChild(rightButton); // Add and remove buttons depending on scroll position. var tabScrollHandler = function () { if (this._tabBar.scrollLeft > 0) { u.addClass(leftButton, this._CssClasses.IS_ACTIVE); } else { u.removeClass(leftButton, this._CssClasses.IS_ACTIVE); } if (this._tabBar.scrollLeft < this._tabBar.scrollWidth - this._tabBar.offsetWidth) { u.addClass(rightButton, this._CssClasses.IS_ACTIVE); } else { u.removeClass(rightButton, this._CssClasses.IS_ACTIVE); } }.bind(this); u.on(this._tabBar, 'scroll', tabScrollHandler); tabScrollHandler(); if (u.hasClass(this._tabBar, this._CssClasses.JS_RIPPLE_EFFECT)) { u.addClass(this._tabBar, this._CssClasses.RIPPLE_IGNORE_EVENTS); } // Select element tabs, document panels var tabs = this._tabBar.querySelectorAll('.' + this._CssClasses.TAB); var panels = this._content.querySelectorAll('.' + this._CssClasses.PANEL); // Create new tabs for each tab element for (var i = 0; i < tabs.length; i++) { new UNavLayoutTab(tabs[i], tabs, panels, this); } } u.addClass(this.element, this._CssClasses.IS_UPGRADED); }, /** * Handles scrolling on the content. * * @private */ _contentScrollHandler: function _contentScrollHandler() { if (u.hasClass(this._header, this._CssClasses.IS_ANIMATING)) { return; } if (this._content.scrollTop > 0 && !u.hasClass(this._header, this._CssClasses.IS_COMPACT)) { u.addClass(this._header, this._CssClasses.CASTING_SHADOW).addClass(this._header, this._CssClasses.IS_COMPACT).addClass(this._header, this._CssClasses.IS_ANIMATING); } else if (this._content.scrollTop <= 0 && u.hasClass(this._header, this._CssClasses.IS_COMPACT)) { u.removeClass(this._header, this._CssClasses.CASTING_SHADOW).removeClass(this._header, this._CssClasses.IS_COMPACT).addClass(this._header, this._CssClasses.IS_ANIMATING); } }, /** * Handles changes in screen size. * * @private */ _screenSizeHandler: function _screenSizeHandler() { if (u.isIE8 || u.isIE9) { this._screenSizeMediaQuery = {}; var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; if (w > 1024) this._screenSizeMediaQuery.matches = false;else this._screenSizeMediaQuery.matches = true; } if (this._screenSizeMediaQuery.matches) { u.addClass(this.element, this._CssClasses.IS_SMALL_SCREEN); } else { u.removeClass(this.element, this._CssClasses.IS_SMALL_SCREEN); // Collapse drawer (if any) when moving to a large screen size. if (this._drawer) { u.removeClass(this._drawer, this._CssClasses.IS_DRAWER_OPEN); u.removeClass(this._obfuscator, this._CssClasses.IS_DRAWER_OPEN); } } }, /** * Handles toggling of the drawer. * * @private */ _drawerToggleHandler: function _drawerToggleHandler() { u.toggleClass(this._drawer, this._CssClasses.IS_DRAWER_OPEN); u.toggleClass(this._obfuscator, this._CssClasses.IS_DRAWER_OPEN); }, /** * Handles (un)setting the `is-animating` class * * @private */ _headerTransitionEndHandler: function _headerTransitionEndHandler() { u.removeClass(this._header, this._CssClasses.IS_ANIMATING); }, /** * Handles expanding the header on click * * @private */ _headerClickHandler: function _headerClickHandler() { if (u.hasClass(this._header, this._CssClasses.IS_COMPACT)) { u.removeClass(this._header, this._CssClasses.IS_COMPACT); u.addClass(this._header, this._CssClasses.IS_ANIMATING); } }, /** * Reset tab state, dropping active classes * * @private */ _resetTabState: function _resetTabState(tabBar) { for (var k = 0; k < tabBar.length; k++) { u.removeClass(tabBar[k], this._CssClasses.IS_ACTIVE); } }, /** * Reset panel state, droping active classes * * @private */ _resetPanelState: function _resetPanelState(panels) { for (var j = 0; j < panels.length; j++) { u.removeClass(panels[j], this._CssClasses.IS_ACTIVE); } }, _navlinkClickHander: function _navlinkClickHander(e) { //var _target = e.currentTarget || e.target || e.srcElement; var curlink = this.element.querySelector('.' + this._CssClasses.NAV_LINK_CURRENT); curlink && u.removeClass(curlink, this._CssClasses.NAV_LINK_CURRENT); // if (curlink && u.isIE8){ // var sub = curlink.parentNode.querySelector('.'+this._CssClasses.NAV_SUB); // if (sub){ // sub.style.maxHeight = '0'; // } // } var item = u.closest(e.target, this._CssClasses.NAV_LINK); if (item) { u.addClass(item, this._CssClasses.NAV_LINK_CURRENT); var sub = item.parentNode.querySelector('.' + this._CssClasses.NAV_SUB), open = u.hasClass(item, this._CssClasses.NAV_LINK_OPEN); if (sub && open) { u.removeClass(item, this._CssClasses.NAV_LINK_OPEN); if (u.isIE8) sub.style.maxHeight = 0; } if (sub && !open) { u.addClass(item, this._CssClasses.NAV_LINK_OPEN); if (u.isIE8) sub.style.maxHeight = '999px'; } // sub && open && u.removeClass(item, this._CssClasses.NAV_LINK_OPEN); // sub && !open && u.addClass(item, this._CssClasses.NAV_LINK_OPEN); } } }); /** * Constructor for an individual tab. * * @constructor * @param {HTMLElement} tab The HTML element for the tab. * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs. * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels. * @param {UNavLayout} layout The UNavLayout object that owns the tab. */ function UNavLayoutTab(tab, tabs, panels, layout) { /** * Auxiliary method to programmatically select a tab in the UI. */ function selectTab() { var href = tab.href.split('#')[1]; var panel = layout._content.querySelector('#' + href); layout._resetTabState(tabs); layout._resetPanelState(panels); u.addClass(tab, layout._CssClasses.IS_ACTIVE); u.addClass(panel, layout._CssClasses.IS_ACTIVE); } //if (layout.tabBar_.classList.contains(layout._CssClasses.JS_RIPPLE_EFFECT)) { var rippleContainer = document.createElement('span'); u.addClass(rippleContainer, 'u-ripple'); //rippleContainer.classList.add(layout._CssClasses.JS_RIPPLE_EFFECT); //var ripple = document.createElement('span'); //ripple.classList.add(layout._CssClasses.RIPPLE); //rippleContainer.appendChild(ripple); tab.appendChild(rippleContainer); new URipple(tab); //} u.on(tab, 'click', function (e) { if (tab.getAttribute('href').charAt(0) === '#') { e.preventDefault(); selectTab(); } }); tab.show = selectTab; u.on(tab, 'click', function (e) { e.preventDefault(); var href = tab.href.split('#')[1]; var panel = layout._content.querySelector('#' + href); layout._resetTabState(tabs); layout._resetPanelState(panels); u.addClass(tab, layout._CssClasses.IS_ACTIVE); u.addClass(panel, layout._CssClasses.IS_ACTIVE); }); } u.NavLayoutTab = UNavLayoutTab; u.compMgr.regComp({ comp: u.NavLayout, compAsString: 'u.NavLayout', css: 'u-navlayout' }); 'use strict'; u.NavMenu = u.BaseComponent.extend({ _Constant: {}, _CssClasses: { NAV: 'u-navmenu', NAV_LINK: 'u-navmenu-link', NAV_LINK_CURRENT: 'u-navmenu-link-current', NAV_LINK_OPEN: 'u-navmenu-link-open', NAV_SUB: 'u-navmenu-sub' }, init: function init() { if (u.hasClass(this.element, 'u-navmenu-horizontal')) { u.on(this.element, 'click', this._horNavlinkClickHander.bind(this)); } else { u.on(this.element, 'click', this._navlinkClickHander.bind(this)); } var items = this.element.querySelectorAll('.' + this._CssClasses.NAV_LINK); for (var i = 0; i < items.length; i++) { new u.Ripple(items[i]); } }, _horNavlinkClickHander: function _horNavlinkClickHander(e) { var item = u.closest(e.target, this._CssClasses.NAV_LINK); if (item) { var curlink = this.element.querySelector('.' + this._CssClasses.NAV_LINK_CURRENT); curlink && u.removeClass(curlink, this._CssClasses.NAV_LINK_CURRENT); u.addClass(item, this._CssClasses.NAV_LINK_CURRENT); } }, _navlinkClickHander: function _navlinkClickHander(e) { //var _target = e.currentTarget || e.target || e.srcElement; var curlink = this.element.querySelector('.' + this._CssClasses.NAV_LINK_CURRENT); curlink && u.removeClass(curlink, this._CssClasses.NAV_LINK_CURRENT); // if (curlink && u.isIE8){ // var sub = curlink.parentNode.querySelector('.'+this._CssClasses.NAV_SUB); // if (sub){ // sub.style.maxHeight = '0'; // } // } var item = u.closest(e.target, this._CssClasses.NAV_LINK); if (item) { u.addClass(item, this._CssClasses.NAV_LINK_CURRENT); var sub = item.parentNode.querySelector('.' + this._CssClasses.NAV_SUB), open = u.hasClass(item, this._CssClasses.NAV_LINK_OPEN); if (sub && open) { u.removeClass(item, this._CssClasses.NAV_LINK_OPEN); if (u.isIE8) sub.style.maxHeight = 0; } if (sub && !open) { u.addClass(item, this._CssClasses.NAV_LINK_OPEN); if (u.isIE8) sub.style.maxHeight = '999px'; } // sub && open && u.removeClass(item, this._CssClasses.NAV_LINK_OPEN); // sub && !open && u.addClass(item, this._CssClasses.NAV_LINK_OPEN); } } }); u.compMgr.regComp({ comp: u.NavMenu, compAsString: 'u.NavMenu', css: 'u-navmenu' }); 'use strict'; u.Text = u.BaseComponent.extend({ _Constant: { NO_MAX_ROWS: -1, MAX_ROWS_ATTRIBUTE: 'maxrows' }, _CssClasses: { LABEL: 'u-label', INPUT: 'u-input', IS_DIRTY: 'is-dirty', IS_FOCUSED: 'is-focused', IS_DISABLED: 'is-disabled', IS_INVALID: 'is-invalid', IS_UPGRADED: 'is-upgraded' }, init: function init() { var oThis = this; this.maxRows = this._Constant.NO_MAX_ROWS; this.label_ = this.element.querySelector('.' + this._CssClasses.LABEL); this._input = this.element.querySelector('input'); if (this._input) { if (this._input.hasAttribute( /** @type {string} */this._Constant.MAX_ROWS_ATTRIBUTE)) { this.maxRows = parseInt(this._input.getAttribute( /** @type {string} */this._Constant.MAX_ROWS_ATTRIBUTE), 10); if (isNaN(this.maxRows)) { this.maxRows = this._Constant.NO_MAX_ROWS; } } this.boundUpdateClassesHandler = this._updateClasses.bind(this); this.boundFocusHandler = this._focus.bind(this); this.boundBlurHandler = this._blur.bind(this); this.boundResetHandler = this._reset.bind(this); this._input.addEventListener('input', this.boundUpdateClassesHandler); if (u.isIE8) { this._input.addEventListener('propertychange', function () { oThis._updateClasses(); }); } this._input.addEventListener('focus', this.boundFocusHandler); if (u.isIE8 || u.isIE9) { if (this.label_) { this.label_.addEventListener('click', function () { this._input.focus(); }.bind(this)); } } this._input.addEventListener('blur', this.boundBlurHandler); this._input.addEventListener('reset', this.boundResetHandler); if (this.maxRows !== this._Constant.NO_MAX_ROWS) { // TODO: This should handle pasting multi line text. // Currently doesn't. this.boundKeyDownHandler = this._down.bind(this); this._input.addEventListener('keydown', this.boundKeyDownHandler); } var invalid = u.hasClass(this.element, this._CssClasses.IS_INVALID); this._updateClasses(); u.addClass(this.element, this._CssClasses.IS_UPGRADED); if (invalid) { u.addClass(this.element, this._CssClasses.IS_INVALID); } } }, /** * Handle input being entered. * * @param {Event} event The event that fired. * @private */ _down: function _down(event) { var currentRowCount = event.target.value.split('\n').length; if (event.keyCode === 13) { if (currentRowCount >= this.maxRows) { event.preventDefault(); } } }, /** * Handle focus. * * @param {Event} event The event that fired. * @private */ _focus: function _focus(event) { u.addClass(this.element, this._CssClasses.IS_FOCUSED); }, /** * Handle lost focus. * * @param {Event} event The event that fired. * @private */ _blur: function _blur(event) { u.removeClass(this.element, this._CssClasses.IS_FOCUSED); }, /** * Handle reset event from out side. * * @param {Event} event The event that fired. * @private */ _reset: function _reset(event) { this._updateClasses(); }, /** * Handle class updates. * * @private */ _updateClasses: function _updateClasses() { this.checkDisabled(); this.checkValidity(); this.checkDirty(); }, // Public methods. /** * Check the disabled state and update field accordingly. * * @public */ checkDisabled: function checkDisabled() { if (this._input.disabled) { u.addClass(this.element, this._CssClasses.IS_DISABLED); } else { u.removeClass(this.element, this._CssClasses.IS_DISABLED); } }, /** * Check the validity state and update field accordingly. * * @public */ checkValidity: function checkValidity() { if (this._input.validity) { if (this._input.validity.valid) { u.removeClass(this.element, this._CssClasses.IS_INVALID); } else { u.addClass(this.element, this._CssClasses.IS_INVALID); } } }, /** * Check the dirty state and update field accordingly. * * @public */ checkDirty: function checkDirty() { if (this._input.value && this._input.value.length > 0) { u.addClass(this.element, this._CssClasses.IS_DIRTY); } else { u.removeClass(this.element, this._CssClasses.IS_DIRTY); } }, /** * Disable text field. * * @public */ disable: function disable() { this._input.disabled = true; this._updateClasses(); }, /** * Enable text field. * * @public */ enable: function enable() { this._input.disabled = false; this._updateClasses(); }, /** * Update text field value. * * @param {string} value The value to which to set the control (optional). * @public */ change: function change(value) { this._input.value = value || ''; this._updateClasses(); } }); //if (u.compMgr) // u.compMgr.addPlug({ // name:'text', // plug: u.Text // }) u.compMgr.regComp({ comp: u.Text, compAsString: 'u.Text', css: 'u-text' }); 'use strict'; u.Menu = u.BaseComponent.extend({ _Keycodes: { ENTER: 13, ESCAPE: 27, SPACE: 32, UP_ARROW: 38, DOWN_ARROW: 40 }, _CssClasses: { BOTTOM_LEFT: 'u-menu-bottom-left', // This is the default. BOTTOM_RIGHT: 'u-menu-bottom-right', TOP_LEFT: 'u-menu-top-left', TOP_RIGHT: 'u-menu-top-right', UNALIGNED: 'u-menu-unaligned' }, init: function init() { // Create container for the menu. var container = document.createElement('div'); u.addClass(container, 'u-menu-container'); this.element.parentElement.insertBefore(container, this.element); this.element.parentElement.removeChild(this.element); container.appendChild(this.element); this._container = container; // Create outline for the menu (shadow and background). var outline = document.createElement('div'); u.addClass(outline, 'u-menu-outline'); this._outline = outline; container.insertBefore(outline, this.element); // Find the "for" element and bind events to it. var forElId = this.element.getAttribute('for') || this.element.getAttribute('data-u-for'); var forEl = null; if (forElId) { forEl = document.getElementById(forElId); if (forEl) { this.for_element = forEl; u.on(forEl, 'click', this._handleForClick.bind(this)); u.on(forEl, 'keydown', this._handleForKeyboardEvent.bind(this)); } } var items = this.element.querySelectorAll('.u-menu-item'); this._boundItemKeydown = this._handleItemKeyboardEvent.bind(this); this._boundItemClick = this._handleItemClick.bind(this); for (var i = 0; i < items.length; i++) { // Add a listener to each menu item. u.on(items[i], 'click', this._boundItemClick); // Add a tab index to each menu item. items[i].tabIndex = '-1'; // Add a keyboard listener to each menu item. u.on(items[i], 'keydown', this._boundItemKeydown); } for (i = 0; i < items.length; i++) { var item = items[i]; var rippleContainer = document.createElement('span'); u.addClass(rippleContainer, 'u-ripple'); item.appendChild(rippleContainer); new URipple(item); } //} // Copy alignment classes to the container, so the outline can use them. if (u.hasClass(this.element, 'u-menu-bottom-left')) { u.addClass(this._outline, 'u-menu-bottom-left'); } if (u.hasClass(this.element, 'u-menu-bottom-right')) { u.addClass(this._outline, 'u-menu-bottom-right'); } if (u.hasClass(this.element, 'u-menu-top-left')) { u.addClass(this._outline, 'u-menu-top-left'); } if (u.hasClass(this.element, 'u-menu-top-right')) { u.addClass(this._outline, 'u-menu-top-right'); } if (u.hasClass(this.element, 'u-menu-unaligned')) { u.addClass(this._outline, 'u-menu-unaligned'); } u.addClass(container, 'is-upgraded'); }, _handleForClick: function _handleForClick(evt) { if (this.element && this.for_element) { var rect = this.for_element.getBoundingClientRect(); var forRect = this.for_element.parentElement.getBoundingClientRect(); if (u.hasClass(this.element, 'u-menu-unaligned')) { // Do not position the menu automatically. Requires the developer to // manually specify position. } else if (u.hasClass(this.element, 'u-menu-bottom-right')) { // Position below the "for" element, aligned to its right. this._container.style.right = forRect.right - rect.right + 'px'; this._container.style.top = this.for_element.offsetTop + this.for_element.offsetHeight + 'px'; } else if (u.hasClass(this.element, 'u-menu-top-left')) { // Position above the "for" element, aligned to its left. this._container.style.left = this.for_element.offsetLeft + 'px'; this._container.style.bottom = forRect.bottom - rect.top + 'px'; } else if (u.hasClass(this.element, 'u-menu-top-right')) { // Position above the "for" element, aligned to its right. this._container.style.right = forRect.right - rect.right + 'px'; this._container.style.bottom = forRect.bottom - rect.top + 'px'; } else { // Default: position below the "for" element, aligned to its left. this._container.style.left = this.for_element.offsetLeft + 'px'; this._container.style.top = this.for_element.offsetTop + this.for_element.offsetHeight + 'px'; } } this.toggle(evt); }, /** * Handles a keyboard event on the "for" element. * * @param {Event} evt The event that fired. * @private */ _handleForKeyboardEvent: function _handleForKeyboardEvent(evt) { if (this.element && this._container && this.for_element) { var items = this.element.querySelectorAll('.u-menu-item:not([disabled])'); if (items && items.length > 0 && u.hasClass(this._container, 'is-visible')) { if (evt.keyCode === this._Keycodes.UP_ARROW) { u.stopEvent(evt); // evt.preventDefault(); items[items.length - 1].focus(); } else if (evt.keyCode === this._Keycodes.DOWN_ARROW) { u.stopEvent(evt); // evt.preventDefault(); items[0].focus(); } } } }, /** * Handles a keyboard event on an item. * * @param {Event} evt The event that fired. * @private */ _handleItemKeyboardEvent: function _handleItemKeyboardEvent(evt) { if (this.element && this._container) { var items = this.element.querySelectorAll('.u-menu-item:not([disabled])'); if (items && items.length > 0 && u.hasClass(this._container, 'is-visible')) { var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target); if (evt.keyCode === this._Keycodes.UP_ARROW) { u.stopEvent(evt); // evt.preventDefault(); if (currentIndex > 0) { items[currentIndex - 1].focus(); } else { items[items.length - 1].focus(); } } else if (evt.keyCode === this._Keycodes.DOWN_ARROW) { u.stopEvent(evt); // evt.preventDefault(); if (items.length > currentIndex + 1) { items[currentIndex + 1].focus(); } else { items[0].focus(); } } else if (evt.keyCode === this._Keycodes.SPACE || evt.keyCode === this._Keycodes.ENTER) { u.stopEvent(evt); // evt.preventDefault(); // Send mousedown and mouseup to trigger ripple. var e = new MouseEvent('mousedown'); evt.target.dispatchEvent(e); e = new MouseEvent('mouseup'); evt.target.dispatchEvent(e); // Send click. evt.target.click(); } else if (evt.keyCode === this._Keycodes.ESCAPE) { u.stopEvent(evt); // evt.preventDefault(); this.hide(); } } } }, /** * Handles a click event on an item. * * @param {Event} evt The event that fired. * @private */ _handleItemClick: function _handleItemClick(evt) { if (evt.target.hasAttribute('disabled')) { u.stopEvent(evt); // evt.stopPropagation(); } else { // Wait some time before closing menu, so the user can see the ripple. this._closing = true; window.setTimeout(function (evt) { this.hide(); this._closing = false; }.bind(this), 150); } }, /** * Calculates the initial clip (for opening the menu) or final clip (for closing * it), and applies it. This allows us to animate from or to the correct point, * that is, the point it's aligned to in the "for" element. * * @param {number} height Height of the clip rectangle * @param {number} width Width of the clip rectangle * @private */ _applyClip: function _applyClip(height, width) { if (u.hasClass(this.element, 'u-menu-unaligned')) { // Do not clip. this.element.style.clip = ''; } else if (u.hasClass(this.element, 'u-menu-bottom-right')) { // Clip to the top right corner of the menu. this.element.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)'; } else if (u.hasClass(this.element, 'u-menu-top-left')) { // Clip to the bottom left corner of the menu. this.element.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)'; } else if (u.hasClass(this.element, 'u-menu-top-right')) { // Clip to the bottom right corner of the menu. this.element.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)'; } else { // Default: do not clip (same as clipping to the top left corner). this.element.style.clip = 'rect(' + 0 + 'px ' + 0 + 'px ' + 0 + 'px ' + 0 + 'px)'; } }, /** * Adds an event listener to clean up after the animation ends. * * @private */ _addAnimationEndListener: function _addAnimationEndListener() { var cleanup = function () { u.off(this.element, 'transitionend', cleanup); // this.element.removeEventListener('transitionend', cleanup); u.off(this.element, 'webkitTransitionEnd', cleanup); // this.element.removeEventListener('webkitTransitionEnd', cleanup); u.removeClass(this.element, 'is-animating'); }.bind(this); // Remove animation class once the transition is done. u.on(this.element, 'transitionend', cleanup); // this.element.addEventListener('transitionend', cleanup); u.on(this.element, 'webkitTransitionEnd', cleanup); // this.element.addEventListener('webkitTransitionEnd', cleanup); }, /** * Displays the menu. * * @public */ show: function show(evt) { if (this.element && this._container && this._outline) { // Measure the inner element. var height = this.element.getBoundingClientRect().height; var width = this.element.getBoundingClientRect().width; if (!width) { var left = this.element.getBoundingClientRect().left; var right = this.element.getBoundingClientRect().right; width = right - left; } if (!height) { var top = this.element.getBoundingClientRect().top; var bottom = this.element.getBoundingClientRect().bottom; height = bottom - top; } // Apply the inner element's size to the container and outline. this._container.style.width = width + 'px'; this._container.style.height = height + 'px'; this._outline.style.width = width + 'px'; this._outline.style.height = height + 'px'; var transitionDuration = 0.24; // Calculate transition delays for individual menu items, so that they fade // in one at a time. var items = this.element.querySelectorAll('.u-menu-item'); for (var i = 0; i < items.length; i++) { var itemDelay = null; if (u.hasClass(this.element, 'u-menu-top-left') || u.hasClass(this.element, 'u-menu-top-right')) { itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's'; } else { itemDelay = items[i].offsetTop / height * transitionDuration + 's'; } items[i].style.transitionDelay = itemDelay; } // Apply the initial clip to the text before we start animating. this._applyClip(height, width); // Wait for the next