/** * Base scroller class used to create the Plugin.DDNodeScroll and Plugin.DDWinScroll. * This class should not be called on it's own, it's designed to be a plugin. * @module dd * @submodule dd-scroll */ /** * Base scroller class used to create the Plugin.DDNodeScroll and Plugin.DDWinScroll. * This class should not be called on it's own, it's designed to be a plugin. * @class Scroll * @extends Base * @namespace DD * @constructor */ var S = function() { S.superclass.constructor.apply(this, arguments); }, WS, NS, HOST = 'host', BUFFER = 'buffer', PARENT_SCROLL = 'parentScroll', WINDOW_SCROLL = 'windowScroll', SCROLL_TOP = 'scrollTop', SCROLL_LEFT = 'scrollLeft', OFFSET_WIDTH = 'offsetWidth', OFFSET_HEIGHT = 'offsetHeight'; S.ATTRS = { /** * Internal config option to hold the node that we are scrolling. Should not be set by the developer. * @attribute parentScroll * @protected * @type Node */ parentScroll: { value: false, setter: function(node) { if (node) { return node; } return false; } }, /** * The number of pixels from the edge of the screen to turn on scrolling. Default: 30 * @attribute buffer * @type Number */ buffer: { value: 30, validator: Y.Lang.isNumber }, /** * The number of milliseconds delay to pass to the auto scroller. Default: 235 * @attribute scrollDelay * @type Number */ scrollDelay: { value: 235, validator: Y.Lang.isNumber }, /** * The host we are plugged into. * @attribute host * @type Object */ host: { value: null }, /** * Turn on window scroll support, default: false * @attribute windowScroll * @type Boolean */ windowScroll: { value: false, validator: Y.Lang.isBoolean }, /** * Allow vertical scrolling, default: true. * @attribute vertical * @type Boolean */ vertical: { value: true, validator: Y.Lang.isBoolean }, /** * Allow horizontal scrolling, default: true. * @attribute horizontal * @type Boolean */ horizontal: { value: true, validator: Y.Lang.isBoolean } }; Y.extend(S, Y.Base, { /** * Tells if we are actively scrolling or not. * @private * @property _scrolling * @type Boolean */ _scrolling: null, /** * Cache of the Viewport dims. * @private * @property _vpRegionCache * @type Object */ _vpRegionCache: null, /** * Cache of the dragNode dims. * @private * @property _dimCache * @type Object */ _dimCache: null, /** * Holder for the Timer object returned from Y.later. * @private * @property _scrollTimer * @type {Y.later} */ _scrollTimer: null, /** * Sets the _vpRegionCache property with an Object containing the dims from the viewport. * @private * @method _getVPRegion */ _getVPRegion: function() { var r = {}, n = this.get(PARENT_SCROLL), b = this.get(BUFFER), ws = this.get(WINDOW_SCROLL), xy = ((ws) ? [] : n.getXY()), w = ((ws) ? 'winWidth' : OFFSET_WIDTH), h = ((ws) ? 'winHeight' : OFFSET_HEIGHT), t = ((ws) ? n.get(SCROLL_TOP) : xy[1]), l = ((ws) ? n.get(SCROLL_LEFT) : xy[0]); r = { top: t + b, right: (n.get(w) + l) - b, bottom: (n.get(h) + t) - b, left: l + b }; this._vpRegionCache = r; return r; }, initializer: function() { var h = this.get(HOST); h.after('drag:start', Y.bind(this.start, this)); h.after('drag:end', Y.bind(this.end, this)); h.on('drag:align', Y.bind(this.align, this)); //TODO - This doesn't work yet?? Y.one('win').on('scroll', Y.bind(function() { this._vpRegionCache = null; }, this)); }, /** * Check to see if we need to fire the scroll timer. If scroll timer is running this will scroll the window. * @private * @method _checkWinScroll * @param {Boolean} move Should we move the window. From Y.later */ _checkWinScroll: function(move) { var r = this._getVPRegion(), ho = this.get(HOST), ws = this.get(WINDOW_SCROLL), xy = ho.lastXY, scroll = false, b = this.get(BUFFER), win = this.get(PARENT_SCROLL), sTop = win.get(SCROLL_TOP), sLeft = win.get(SCROLL_LEFT), w = this._dimCache.w, h = this._dimCache.h, bottom = xy[1] + h, top = xy[1], right = xy[0] + w, left = xy[0], nt = top, nl = left, st = sTop, sl = sLeft; if (this.get('horizontal')) { if (left <= r.left) { scroll = true; nl = xy[0] - ((ws) ? b : 0); sl = sLeft - b; } if (right >= r.right) { scroll = true; nl = xy[0] + ((ws) ? b : 0); sl = sLeft + b; } } if (this.get('vertical')) { if (bottom >= r.bottom) { scroll = true; nt = xy[1] + ((ws) ? b : 0); st = sTop + b; } if (top <= r.top) { scroll = true; nt = xy[1] - ((ws) ? b : 0); st = sTop - b; } } if (st < 0) { st = 0; nt = xy[1]; } if (sl < 0) { sl = 0; nl = xy[0]; } if (nt < 0) { nt = xy[1]; } if (nl < 0) { nl = xy[0]; } if (move) { ho.actXY = [nl, nt]; ho._alignNode([nl, nt], true); //We are srolling.. xy = ho.actXY; ho.actXY = [nl, nt]; ho._moveNode({ node: win, top: st, left: sl}); if (!st && !sl) { this._cancelScroll(); } } else { if (scroll) { this._initScroll(); } else { this._cancelScroll(); } } }, /** * Cancel a previous scroll timer and init a new one. * @private * @method _initScroll */ _initScroll: function() { this._cancelScroll(); this._scrollTimer = Y.Lang.later(this.get('scrollDelay'), this, this._checkWinScroll, [true], true); }, /** * Cancel a currently running scroll timer. * @private * @method _cancelScroll */ _cancelScroll: function() { this._scrolling = false; if (this._scrollTimer) { this._scrollTimer.cancel(); delete this._scrollTimer; } }, /** * Called from the drag:align event to determine if we need to scroll. * @method align */ align: function(e) { if (this._scrolling) { this._cancelScroll(); e.preventDefault(); } if (!this._scrolling) { this._checkWinScroll(); } }, /** * Set the cache of the dragNode dims. * @private * @method _setDimCache */ _setDimCache: function() { var node = this.get(HOST).get('dragNode'); this._dimCache = { h: node.get(OFFSET_HEIGHT), w: node.get(OFFSET_WIDTH) }; }, /** * Called from the drag:start event * @method start */ start: function() { this._setDimCache(); }, /** * Called from the drag:end event * @method end */ end: function() { this._dimCache = null; this._cancelScroll(); } }); Y.namespace('Plugin'); /** * Extends the Scroll class to make the window scroll while dragging. * @class DDWindowScroll * @extends Scroll * @namespace Plugin * @constructor */ WS = function() { WS.superclass.constructor.apply(this, arguments); }; WS.ATTRS = Y.merge(S.ATTRS, { /** * Turn on window scroll support, default: true * @attribute windowScroll * @type Boolean */ windowScroll: { value: true, setter: function(scroll) { if (scroll) { this.set(PARENT_SCROLL, Y.one('win')); } return scroll; } } }); Y.extend(WS, S, { //Shouldn't have to do this.. initializer: function() { this.set('windowScroll', this.get('windowScroll')); } }); /** * The Scroll instance will be placed on the Drag instance under the winscroll namespace. * @property NS * @default winscroll * @readonly * @protected * @static * @type {String} */ WS.NAME = WS.NS = 'winscroll'; Y.Plugin.DDWinScroll = WS; /** * Extends the Scroll class to make a parent node scroll while dragging. * @class DDNodeScroll * @extends Scroll * @namespace Plugin * @constructor */ NS = function() { NS.superclass.constructor.apply(this, arguments); }; NS.ATTRS = Y.merge(S.ATTRS, { /** * The node we want to scroll. Used to set the internal parentScroll attribute. * @attribute node * @type Node */ node: { value: false, setter: function(node) { var n = Y.one(node); if (!n) { if (node !== false) { Y.error('DDNodeScroll: Invalid Node Given: ' + node); } } else { this.set(PARENT_SCROLL, n); } return n; } } }); Y.extend(NS, S, { //Shouldn't have to do this.. initializer: function() { this.set('node', this.get('node')); } }); /** * The NodeScroll instance will be placed on the Drag instance under the nodescroll namespace. * @property NS * @default nodescroll * @readonly * @protected * @static * @type {String} */ NS.NAME = NS.NS = 'nodescroll'; Y.Plugin.DDNodeScroll = NS; Y.DD.Scroll = S;