/** * @ignore * scroll-view control * @author yiminghe@gmail.com */ KISSY.add('scroll-view/base', function (S, Node, Anim, Container, Render, undefined) { var $ = S.all, isTouchEventSupported = S.Features.isTouchEventSupported(), KeyCode = Node.KeyCode; function onElScroll() { var self = this, el = self.el, scrollTop = el.scrollTop, scrollLeft = el.scrollLeft; if (scrollTop) { self.set('scrollTop', scrollTop + self.get('scrollTop')); } if (scrollLeft) { self.set('scrollLeft', scrollLeft + self.get('scrollLeft')); } el.scrollTop = el.scrollLeft = 0; } function frame(anim, fx) { anim.scrollView.set(fx.prop, fx.val); } /** * Make container scrollable. * module scroll-view will be this class on non-touch device * @class KISSY.ScrollView.Base * @extend KISSY.Component.Container */ return Container.extend({ initializer: function () { this.scrollAnims = []; }, bindUI: function () { var self = this, $el = self.$el; $el.on('mousewheel', self.handleMouseWheel, self) // textarea enter cause el to scroll // bug: left top scroll does not fire scroll event, because scrollTop is 0! .on('scroll', onElScroll, self); }, handleKeyDownInternal: function (e) { // no need to process disabled (already processed by Component) var target = e.target, $target = $(target), nodeName = $target.nodeName(); // editable element if (nodeName == 'input' || nodeName == 'textarea' || nodeName == 'select' || $target.hasAttr('contenteditable')) { return undefined; } var self = this, keyCode = e.keyCode, scrollStep = self.getScrollStep(), ok = undefined; var allowX = self.allowScroll['left']; var allowY = self.allowScroll['top']; if (allowY) { var scrollStepY = scrollStep.top, clientHeight = self.clientHeight, scrollTop = self.get('scrollTop'); if (keyCode == KeyCode.DOWN) { self.scrollToWithBounds({ top: scrollTop + scrollStepY }); ok = true; } else if (keyCode == KeyCode.UP) { self.scrollToWithBounds({top: scrollTop - scrollStepY}); ok = true; } else if (keyCode == KeyCode.PAGE_DOWN) { self.scrollToWithBounds({top: scrollTop + clientHeight}); ok = true; } else if (keyCode == KeyCode.PAGE_UP) { self.scrollToWithBounds({top: scrollTop - clientHeight}); ok = true; } } if (allowX) { var scrollStepX = scrollStep.left; var scrollLeft = self.get('scrollLeft'); if (keyCode == KeyCode.RIGHT) { self.scrollToWithBounds({left: scrollLeft + scrollStepX}); ok = true; } else if (keyCode == KeyCode.LEFT) { self.scrollToWithBounds({left: scrollLeft - scrollStepX}); ok = true; } } return ok; }, getScrollStep: function () { var control = this; if (control.scrollStep) { return control.scrollStep; } var elDoc = $(this.get('el')[0].ownerDocument); var clientHeight = control.clientHeight; var clientWidth = control.clientWidth; return control.scrollStep = { top: Math.max(clientHeight * clientHeight * 0.7 / elDoc.height(), 20), left: Math.max(clientWidth * clientWidth * 0.7 / elDoc.width(), 20) }; }, handleMouseWheel: function (e) { if (this.get('disabled')) { return; } var max, min, self = this, scrollStep = self.getScrollStep(), deltaY, deltaX, maxScroll = self.maxScroll, minScroll = self.minScroll; if ((deltaY = e.deltaY) && self.allowScroll['top']) { var scrollTop = self.get('scrollTop'); max = maxScroll.top; min = minScroll.top; if (scrollTop <= min && deltaY > 0 || scrollTop >= max && deltaY < 0) { } else { self.scrollToWithBounds({top: scrollTop - e.deltaY * scrollStep['top']}); e.preventDefault(); } } if ((deltaX = e.deltaX) && self.allowScroll['left']) { var scrollLeft = self.get('scrollLeft'); max = maxScroll.left; min = minScroll.left; if (scrollLeft <= min && deltaX > 0 || scrollLeft >= max && deltaX < 0) { } else { self.scrollToWithBounds({left: scrollLeft - e.deltaX * scrollStep['left']}); e.preventDefault(); } } }, 'isAxisEnabled': function (axis) { return this.allowScroll[axis == 'x' ? 'left' : 'top']; }, stopAnimation: function () { var self = this; if (self.scrollAnims.length) { S.each(self.scrollAnims, function (scrollAnim) { scrollAnim.stop(); }); self.scrollAnims = []; } self.scrollToWithBounds({ left: self.get('scrollLeft'), top: self.get('scrollTop') }); }, '_uiSetPageIndex': function (v) { this.scrollToPage(v); }, _getPageIndexFromXY: function (v, allowX, direction) { var pagesOffset = this.pagesOffset.concat([]); var p2 = allowX ? 'left' : 'top'; var i, offset; pagesOffset.sort(function (e1, e2) { return e1[p2] - e2[p2]; }); if (direction > 0) { for (i = 0; i < pagesOffset.length; i++) { offset = pagesOffset[i]; if (offset[p2] >= v) { return offset.index; } } } else { for (i = pagesOffset.length - 1; i >= 0; i--) { offset = pagesOffset[i]; if (offset[p2] <= v) { return offset.index; } } } return undefined; }, scrollToPage: function (index, animCfg) { var self = this, pageOffset; if ((pageOffset = self.pagesOffset) && pageOffset[index]) { self.set('pageIndex', index); self.scrollTo(pageOffset[index], animCfg); } }, scrollToWithBounds: function (cfg, anim) { var self = this; var maxScroll = self.maxScroll; var minScroll = self.minScroll; if (cfg.left) { cfg.left = Math.min(Math.max(cfg.left, minScroll.left), maxScroll.left); } if (cfg.top) { cfg.top = Math.min(Math.max(cfg.top, minScroll.top), maxScroll.top); } self.scrollTo(cfg, anim); }, scrollTo: function (cfg, animCfg) { var self = this, left = cfg.left, top = cfg.top; if (animCfg) { var scrollLeft = self.get('scrollLeft'), scrollTop = self.get('scrollTop'), node = {}, to = {}; if (left !== undefined) { to.scrollLeft = left; node.scrollLeft = self.get('scrollLeft'); } if (top !== undefined) { to.scrollTop = top; node.scrollTop = self.get('scrollTop'); } animCfg.frame = frame; animCfg.node = node; animCfg.to = to; var anim; self.scrollAnims.push(anim = new Anim(animCfg)); anim.scrollView = self; anim.run(); } else { if (left !== undefined) { self.set('scrollLeft', left); } if (top !== undefined) { self.set('scrollTop', top); } } } }, { ATTRS: { /** * content element of scroll view component * @property contentEl * @type {KISSY.NodeList} */ /** * @ignore */ contentEl: { }, /** * scrollLeft of scroll view * @property scrollLeft * @type {Number} */ /** * @ignore */ scrollLeft: { view: 1, value: 0 }, /** * scrollTop of scroll view * @property scrollTop * @type {Number} */ /** * @ignore */ scrollTop: { view: 1, value: 0 }, focusable: { // need process keydown value: !isTouchEventSupported }, allowTextSelection: { value: true }, handleMouseEvents: { value: false }, /** * whether to allow snap effect * @cfg {Boolean} snap */ /** * @ignore */ snap: { value: false }, /** * pageIndex, current pageIndex if allow snap * @property pageIndex * @type {Number} */ /** * @ignore */ pageIndex: { value: 0 }, xrender: { value: Render } }, xclass: 'scroll-view' }); }, { requires: ['node', 'anim', 'component/container', './base/render'] });