1/** 2* Gumby Fixed 3*/ 4!function($) { 5 6 'use strict'; 7 8 function Fixed($el) { 9 10 Gumby.debug('Initializing Fixed Position', $el); 11 12 this.$el = $el; 13 14 this.$window = $(window); 15 this.fixedPoint = ''; 16 this.pinPoint = false; 17 this.fixedPointjQ = false; 18 this.pinPointjQ = false; 19 this.offset = 0; 20 this.pinOffset = 0; 21 this.top = 0; 22 this.constrainEl = true; 23 this.state = false; 24 this.measurements = { 25 left: 0, 26 width: 0 27 }; 28 29 // set up module based on attributes 30 this.setup(); 31 32 var scope = this; 33 34 // monitor scroll and update fixed elements accordingly 35 this.$window.on('scroll load', function() { 36 scope.monitorScroll(); 37 }); 38 39 // reinitialize event listener 40 this.$el.on('gumby.initialize', function() { 41 Gumby.debug('Re-initializing Fixed Position', $el); 42 scope.setup(); 43 scope.monitorScroll(); 44 }); 45 } 46 47 // set up module based on attributes 48 Fixed.prototype.setup = function() { 49 var scope = this; 50 51 this.fixedPoint = this.parseAttrValue(Gumby.selectAttr.apply(this.$el, ['fixed'])); 52 53 // pin point is optional 54 this.pinPoint = Gumby.selectAttr.apply(this.$el, ['pin']) || false; 55 56 // offset from fixed point 57 this.offset = Number(Gumby.selectAttr.apply(this.$el, ['offset'])) || 0; 58 59 // offset from pin point 60 this.pinOffset = Number(Gumby.selectAttr.apply(this.$el, ['pinoffset'])) || 0; 61 62 // top position when fixed 63 this.top = Number(Gumby.selectAttr.apply(this.$el, ['top'])) || 0; 64 65 // constrain can be turned off 66 this.constrainEl = Gumby.selectAttr.apply(this.$el, ['constrain']) || true; 67 if(this.constrainEl === 'false') { 68 this.constrainEl = false; 69 } 70 71 // reference to the parent, row/column 72 this.$parent = this.$el.parents('.columns, .column, .row'); 73 this.$parent = this.$parent.length ? this.$parent.first() : false; 74 this.parentRow = this.$parent ? !!this.$parent.hasClass('row') : false; 75 76 // if optional pin point set then parse now 77 if(this.pinPoint) { 78 this.pinPoint = this.parseAttrValue(this.pinPoint); 79 } 80 81 this.fixedPointjQ = this.fixedPoint instanceof jQuery; 82 this.pinPointjQ = this.pinPoint instanceof jQuery; 83 84 // if we have a parent constrain dimenions 85 if(this.$parent && this.constrainEl) { 86 // measure up 87 this.measure(); 88 // and on resize reset measurement 89 this.$window.resize(function() { 90 if(scope.state) { 91 scope.measure(); 92 scope.constrain(); 93 } 94 }); 95 } 96 }; 97 98 // monitor scroll and trigger changes based on position 99 Fixed.prototype.monitorScroll = function() { 100 var scrollAmount = this.$window.scrollTop(), 101 // recalculate selector attributes as position may have changed 102 fixedPoint = this.fixedPointjQ ? this.fixedPoint.offset().top : this.fixedPoint, 103 pinPoint = false, 104 timer; 105 106 // if a pin point is set recalculate 107 if(this.pinPoint) { 108 pinPoint = this.pinPointjQ ? this.pinPoint.offset().top : this.pinPoint; 109 } 110 111 // apply offsets 112 if(this.offset) { fixedPoint -= this.offset; } 113 if(this.pinOffset) { pinPoint -= this.pinOffset; } 114 115 // fix it 116 if((scrollAmount >= fixedPoint) && this.state !== 'fixed') { 117 if(!pinPoint || scrollAmount < pinPoint) { 118 this.fix(); 119 } 120 // unfix it 121 } else if(scrollAmount < fixedPoint && this.state === 'fixed') { 122 this.unfix(); 123 124 // pin it 125 } else if(pinPoint && scrollAmount >= pinPoint && this.state !== 'pinned') { 126 this.pin(); 127 } 128 }; 129 130 // fix the element and update state 131 Fixed.prototype.fix = function() { 132 Gumby.debug('Element has been fixed', this.$el); 133 Gumby.debug('Triggering onFixed event', this.$el); 134 135 this.state = 'fixed'; 136 this.$el.css({ 137 'top' : this.top 138 }).addClass('fixed').removeClass('unfixed pinned').trigger('gumby.onFixed'); 139 140 // if we have a parent constrain dimenions 141 if(this.$parent) { 142 this.constrain(); 143 } 144 }; 145 146 // unfix the element and update state 147 Fixed.prototype.unfix = function() { 148 Gumby.debug('Element has been unfixed', this.$el); 149 Gumby.debug('Triggering onUnfixed event', this.$el); 150 151 this.state = 'unfixed'; 152 this.$el.addClass('unfixed').removeClass('fixed pinned').trigger('gumby.onUnfixed'); 153 }; 154 155 // pin the element in position 156 Fixed.prototype.pin = function() { 157 Gumby.debug('Element has been pinned', this.$el); 158 Gumby.debug('Triggering onPinned event', this.$el); 159 this.state = 'pinned'; 160 this.$el.css({ 161 'top' : this.$el.offset().top 162 }).addClass('pinned fixed').removeClass('unfixed').trigger('gumby.onPinned'); 163 }; 164 165 // constrain elements dimensions to match width/height 166 Fixed.prototype.constrain = function() { 167 Gumby.debug("Constraining element", this.$el); 168 this.$el.css({ 169 left: this.measurements.left, 170 width: this.measurements.width 171 }); 172 }; 173 174 // measure up the parent for constraining 175 Fixed.prototype.measure = function() { 176 var parentPadding; 177 178 this.measurements.left = this.$parent.offset().left; 179 this.measurements.width = this.$parent.width(); 180 181 // if element has a parent row then need to consider padding 182 if(this.parentRow) { 183 parentPadding = Number(this.$parent.css('paddingLeft').replace(/px/, '')); 184 if(parentPadding) { 185 this.measurements.left += parentPadding; 186 } 187 } 188 }; 189 190 // parse attribute values, could be px, top, selector 191 Fixed.prototype.parseAttrValue = function(attr) { 192 // px value fixed point 193 if($.isNumeric(attr)) { 194 return Number(attr); 195 // 'top' string fixed point 196 } else if(attr === 'top') { 197 return this.$el.offset().top; 198 // selector specified 199 } else { 200 var $el = $(attr); 201 if(!$el.length) { 202 Gumby.error('Cannot find Fixed target: '+attr); 203 return false; 204 } 205 return $el; 206 } 207 }; 208 209 // add initialisation 210 Gumby.addInitalisation('fixed', function(all) { 211 $('[data-fixed],[gumby-fixed],[fixed]').each(function() { 212 var $this = $(this); 213 214 // this element has already been initialized 215 // and we're only initializing new modules 216 if($this.data('isFixed') && !all) { 217 return true; 218 219 // this element has already been initialized 220 // and we need to reinitialize it 221 } else if($this.data('isFixed') && all) { 222 $this.trigger('gumby.initialize'); 223 return true; 224 } 225 226 // mark element as initialized 227 $this.data('isFixed', true); 228 new Fixed($this); 229 }); 230 }); 231 232 // register UI module 233 Gumby.UIModule({ 234 module: 'fixed', 235 events: ['initialize', 'onFixed', 'onUnfixed'], 236 init: function() { 237 Gumby.initialize('fixed'); 238 } 239 }); 240}(jQuery); 241