iuap-design
Version:
UI Framework Used For Enterprise.
1,382 lines (1,260 loc) • 467 kB
JavaScript
'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