File: js\affix.js
/**
A plugin that makes a node stick to a position when it scrolls over a certain
offset. Based on Bootstrap's Affix plugin.
@class Plugin.Affix
@extends Plugin.Base
@constructor
@param {Object} config Object literal containing configuration options
@param {Object} config.host Node or widget (the boundingBox will be used)
@param {Object|Number} [config.offset] The offset to use. If not found it will
default to data-offset-top, data-offset-left or data-offset properties on
the host
@param {Number} [config.offset.left] Left offset to use. At least one of top or
left should be used
@param {Number} [config.offset.top] Top offset to use.
**/
function Affix() {
Affix.superclass.constructor.apply(this, arguments);
}
Y.extend(Affix, Y.Plugin.Base, {
/**
Get the correct node when plugged in Nodes or Widgets
@method _getNode
@return Node
@private
**/
_getNode: function () {
var node;
if (!this._node) {
node = this.get('host');
if (Y.Widget && node instanceof Y.Widget) {
node = node.get('boundingBox');
}
this._node = node;
}
return this._node;
},
initializer: function () {
var node = this._getNode(),
xy = node.getXY();
/**
The original left position of the node. Used to calculate if the node is
over the offset.
@property _x
@type Number
@private
**/
this._x = xy[0];
/**
The original top position of the node. Used to calculate if the node is
over the offset.
@property _y
@type Number
@private
**/
this._y = xy[1];
/**
Event handle for the document's `scroll` event.
@property _handle
@type Object
@private
**/
this._handle = Y.on('scroll', Y.throttle(Y.bind(this.refresh, this), 15));
this.refresh();
},
/**
Fixes or releases the node according to the scroll position.
Called automatically when scrolling.
@method refresh
**/
refresh: function () {
var offset = this.get('offset'),
offsetLeft = offset.left,
offsetTop = offset.top,
// do the math for both directions even though it may be set for
// only one direction for simplicity
isOverOffset = (this._y - Y.DOM.docScrollY() < offsetTop) ||
(this._x - Y.DOM.docScrollX() < offsetLeft);
// reset position styles if no offset was provided in that direction
// because if an inline style was applied it'll break sooner or
// later because of the changed to "fixed" position
this._node.setStyles({
position: isOverOffset ? 'fixed' : '',
left: isOverOffset && offsetLeft ? (offsetLeft + 'px') : '',
top: isOverOffset && offsetTop ? (offsetTop + 'px') : ''
});
},
destructor: function () {
this._handle.detach();
this._handle = this._node = null;
}
}, {
ATTRS: {
offset: {
setter: function (value) {
return typeof value === 'number' ? {
top: value,
left: value
} : value;
},
valueFn: function () {
var node = this._getNode(),
data = parseInt(node.getData('offset'), 10),
offset = {};
if (Y.Lang.isNumber(data)) {
offset.top = offset.left = data;
} else {
offset.left = parseInt(node.getData('offset-left'), 10);
offset.top = parseInt(node.getData('offset-top'), 10);
}
if (!offset.left && !offset.top) { Y.log('no offset provided', 'info'); }
return offset;
}
}
}
});
/**
Plugin namespace
@property NS
@type String
@default "affix"
@static
**/
Affix.NS = 'affix';
Y.namespace('Plugin').Affix = Affix;