/** * Gumby Fixed */ !function($) { 'use strict'; function Fixed($el) { Gumby.debug('Initializing Fixed Position', $el); this.$el = $el; this.$window = $(window); this.fixedPoint = ''; this.pinPoint = false; this.fixedPointjQ = false; this.pinPointjQ = false; this.offset = 0; this.pinOffset = 0; this.top = 0; this.constrainEl = true; this.state = false; this.measurements = { left: 0, width: 0 }; // set up module based on attributes this.setup(); var scope = this; // monitor scroll and update fixed elements accordingly this.$window.on('scroll load', function() { scope.monitorScroll(); }); // reinitialize event listener this.$el.on('gumby.initialize', function() { Gumby.debug('Re-initializing Fixed Position', $el); scope.setup(); scope.monitorScroll(); }); } // set up module based on attributes Fixed.prototype.setup = function() { var scope = this; this.fixedPoint = this.parseAttrValue(Gumby.selectAttr.apply(this.$el, ['fixed'])); // pin point is optional this.pinPoint = Gumby.selectAttr.apply(this.$el, ['pin']) || false; // offset from fixed point this.offset = Number(Gumby.selectAttr.apply(this.$el, ['offset'])) || 0; // offset from pin point this.pinOffset = Number(Gumby.selectAttr.apply(this.$el, ['pinoffset'])) || 0; // top position when fixed this.top = Number(Gumby.selectAttr.apply(this.$el, ['top'])) || 0; // constrain can be turned off this.constrainEl = Gumby.selectAttr.apply(this.$el, ['constrain']) || true; if(this.constrainEl === 'false') { this.constrainEl = false; } // reference to the parent, row/column this.$parent = this.$el.parents('.columns, .column, .row'); this.$parent = this.$parent.length ? this.$parent.first() : false; this.parentRow = this.$parent ? !!this.$parent.hasClass('row') : false; // if optional pin point set then parse now if(this.pinPoint) { this.pinPoint = this.parseAttrValue(this.pinPoint); } this.fixedPointjQ = this.fixedPoint instanceof jQuery; this.pinPointjQ = this.pinPoint instanceof jQuery; // if we have a parent constrain dimenions if(this.$parent && this.constrainEl) { // measure up this.measure(); // and on resize reset measurement this.$window.resize(function() { if(scope.state) { scope.measure(); scope.constrain(); } }); } }; // monitor scroll and trigger changes based on position Fixed.prototype.monitorScroll = function() { var scrollAmount = this.$window.scrollTop(), // recalculate selector attributes as position may have changed fixedPoint = this.fixedPointjQ ? this.fixedPoint.offset().top : this.fixedPoint, pinPoint = false, timer; // if a pin point is set recalculate if(this.pinPoint) { pinPoint = this.pinPointjQ ? this.pinPoint.offset().top : this.pinPoint; } // apply offsets if(this.offset) { fixedPoint -= this.offset; } if(this.pinOffset) { pinPoint -= this.pinOffset; } // fix it if((scrollAmount >= fixedPoint) && this.state !== 'fixed') { if(!pinPoint || scrollAmount < pinPoint) { this.fix(); } // unfix it } else if(scrollAmount < fixedPoint && this.state === 'fixed') { this.unfix(); // pin it } else if(pinPoint && scrollAmount >= pinPoint && this.state !== 'pinned') { this.pin(); } }; // fix the element and update state Fixed.prototype.fix = function() { Gumby.debug('Element has been fixed', this.$el); Gumby.debug('Triggering onFixed event', this.$el); this.state = 'fixed'; this.$el.css({ 'top' : this.top }).addClass('fixed').removeClass('unfixed pinned').trigger('gumby.onFixed'); // if we have a parent constrain dimenions if(this.$parent) { this.constrain(); } }; // unfix the element and update state Fixed.prototype.unfix = function() { Gumby.debug('Element has been unfixed', this.$el); Gumby.debug('Triggering onUnfixed event', this.$el); this.state = 'unfixed'; this.$el.addClass('unfixed').removeClass('fixed pinned').trigger('gumby.onUnfixed'); }; // pin the element in position Fixed.prototype.pin = function() { Gumby.debug('Element has been pinned', this.$el); Gumby.debug('Triggering onPinned event', this.$el); this.state = 'pinned'; this.$el.css({ 'top' : this.$el.offset().top }).addClass('pinned fixed').removeClass('unfixed').trigger('gumby.onPinned'); }; // constrain elements dimensions to match width/height Fixed.prototype.constrain = function() { Gumby.debug("Constraining element", this.$el); this.$el.css({ left: this.measurements.left, width: this.measurements.width }); }; // measure up the parent for constraining Fixed.prototype.measure = function() { var parentPadding; this.measurements.left = this.$parent.offset().left; this.measurements.width = this.$parent.width(); // if element has a parent row then need to consider padding if(this.parentRow) { parentPadding = Number(this.$parent.css('paddingLeft').replace(/px/, '')); if(parentPadding) { this.measurements.left += parentPadding; } } }; // parse attribute values, could be px, top, selector Fixed.prototype.parseAttrValue = function(attr) { // px value fixed point if($.isNumeric(attr)) { return Number(attr); // 'top' string fixed point } else if(attr === 'top') { return this.$el.offset().top; // selector specified } else { var $el = $(attr); if(!$el.length) { Gumby.error('Cannot find Fixed target: '+attr); return false; } return $el; } }; // add initialisation Gumby.addInitalisation('fixed', function(all) { $('[data-fixed],[gumby-fixed],[fixed]').each(function() { var $this = $(this); // this element has already been initialized // and we're only initializing new modules if($this.data('isFixed') && !all) { return true; // this element has already been initialized // and we need to reinitialize it } else if($this.data('isFixed') && all) { $this.trigger('gumby.initialize'); return true; } // mark element as initialized $this.data('isFixed', true); new Fixed($this); }); }); // register UI module Gumby.UIModule({ module: 'fixed', events: ['initialize', 'onFixed', 'onUnfixed'], init: function() { Gumby.initialize('fixed'); } }); }(jQuery);