agenda-libre-ruby/public/webshims/shims/sticky.js

586 lines
14 KiB
JavaScript
Raw Normal View History

webshim.register('sticky', function($, webshim, window, document, undefined, featureOptions){
"use strict";
var uid = 0;
var stickys = 0;
var $window = $(window);
function getCssValue(property, value, noPrefixes) {
var prop = property + ':',
el = document.createElement('test'),
mStyle = el.style;
if (!noPrefixes) {
mStyle.cssText = prop + [ '-webkit-', '-moz-', '-ms-', '-o-', '' ].join(value + ';' + prop) + value + ';';
} else {
mStyle.cssText = prop + value;
}
return mStyle[ property ];
}
function getPos() {
return {
top: $.css(this, 'top'),
bottom: $.css(this, 'bottom')
};
}
var getWinScroll = (function () {
var docElem;
var prop = 'pageYOffset';
return (prop in window) ?
function () {
return window[ prop ];
} :
((docElem = document.documentElement), function () {
return docElem.scrollTop;
})
;
})();
var isTouch = 'ontouchstart' in window || window.matchMedia('(max-device-width: 721px)').matches;
var support = {
fixed: getCssValue('position', 'fixed', true),
sticky: getCssValue('position', 'sticky')
};
var stickyMixin = {
getPosition: function () {
if(!this.isSticky){
this.position = {
top: this.$el.css('top'),
bottom: this.$el.css('bottom')
};
if (((
(this.position.top != 'auto' && this.position.bottom != 'auto') ||
this.position.top == 'auto' && this.position.bottom == 'auto')) && this.$el.css('position') == 'static') {
this.position = $.swap(this.$el[0], {position: 'absolute'}, getPos);
}
if (this.position.top !== 'auto') {
this.ankered = 'top';
} else if (this.position.bottom !== 'auto') {
this.ankered = 'bottom';
}
if(this.ankered == 'top'){
this.position.top = parseFloat(this.position.top, 10) || 0;
} else if(this.ankered == 'bottom'){
this.position.bottom = parseFloat(this.position.bottom, 10) || 0;
}
}
},
update: function (full) {
if (!this.disabled && this.$el[0].offsetWidth) {
if (full) {
if(this.isSticky){
this.removeSticky();
}
this.getPosition();
}
this.updateDimension();
}
},
setTdWidth: function(){
if(this.isTable){
this.$el.find('td, th').each(this._setInlineWidth);
}
},
_setInlineWidth: function(){
$.data(this, 'inlineWidth', this.style.width);
$(this).innerWidth($(this).innerWidth());
},
_restoreInlineWidth: function(){
this.style.width = $.data(this, 'inlineWidth') || '';
$.removeData(this, 'inlineWidth');
},
removeSticky: function(){
this.$el.removeClass('ws-sticky-on');
this.$el.css(this.stickyData.inline);
this.$placeholder.detach();
this.isSticky = false;
if(this.isTable){
this.$el.find('td, th').each(this._restoreInlineWidth);
}
},
commonAddEvents: function(){
var enableDisable;
var that = this;
var update = function() {
that.update();
};
var stickyMedia = this.$el.data('stickymedia');
var media = window.matchMedia && stickyMedia ? matchMedia(stickyMedia) : false;
$window.one('load', update);
$(document).on('updateshadowdom' + this.evtid, update);
this.$el.on('updatesticky'+ this.evtid, function(e){
that.update(true);
e.stopPropagation();
});
this.$el.on('disablesticky'+ this.evtid, function(e){
that.disable(true);
e.stopPropagation();
});
this.$el.on('enablesticky'+ this.evtid, function(e){
that.disable(false);
e.stopPropagation();
});
this.$el.on('remove'+ this.evtid+' destroysticky'+ this.evtid, function(e) {
$window.off(that.evtid);
$(document).off(that.evtid);
that.$el.off(that.evtid);
that.$parent.off(that.evtid);
that.$el.removeData('wsSticky').removeClass('ws-sticky');
if (that.$placeholder) {
that.$el.removeClass('ws-sticky-on');
that.$placeholder.remove();
}
stickys--;
e.stopPropagation();
});
if(media && media.addListener){
enableDisable = function(){
that.disable(!media.matches);
};
media.addListener(enableDisable);
enableDisable();
}
},
disable: function(disable){
if(!arguments.length){
return this.disabled;
}
if(this.disabled != disable){
this.disabled = !!disable;
if(this.disabled){
if(this.isSticky){
this.removeSticky();
}
} else {
this.update(true);
}
}
},
setSticky: function(){
if (!this.$placeholder) {
this.$placeholder = this.isTable ? $(this.$el[0].cloneNode(true)) : $(document.createElement(this.$el[0].nodeName || 'div'));
this.$placeholder.addClass('ws-fixedsticky-placeholder').removeClass('ws-sticky');
}
this.setTdWidth();
this.$placeholder
.insertAfter(this.$el)
.outerHeight(this.stickyData.outerHeight, true)
.outerWidth(this.stickyData.outerWidth)
;
this.isSticky = true;
this.$el.addClass('ws-sticky-on');
if(!this.isTable){
if( this.stickyData.width != this.$el.width()){
this.$el.width(this.stickyData.width);
}
}
},
getCommonStickyData: function(){
var marginTop = (parseFloat(this.$el.css('marginTop'), 10) || 0);
this.stickyData.scrollTop = this.stickyData.top - marginTop;
this.stickyData.outerHeight = this.$el.outerHeight(true);
this.stickyData.bottom = this.stickyData.top + this.stickyData.outerHeight - marginTop;
this.stickyData.width = this.$el.width();
this.stickyData.outerWidth = this.$el.outerWidth();
this.stickyData.marginLeft = parseFloat(this.$el.css('marginLeft'), 10) || 0;
this.stickyData.offsetLeft = this.$el[0].offsetLeft;
this.stickyData.inline.width = this.elStyle.width;
this.stickyData.inline.marginLeft = this.elStyle.marginLeft;
if(this.ankered == 'top'){
this.stickyData.inline.top = this.elStyle.top;
} else if(this.ankered == 'bottom'){
this.stickyData.inline.bottom = this.elStyle.bottom;
}
},
getCommonParentData: function(){
this.parentData.paddingTop = (parseFloat(this.$parent.css('paddingTop'), 10) || 0);
this.parentData.offsetTop = this.$parent.offset().top;
this.parentData.top = this.parentData.offsetTop + (parseFloat(this.$parent.css('borderTopWidth'), 10) || 0) + this.parentData.paddingTop;
this.parentData.height = this.$parent.height();
this.parentData.bottom = this.parentData.top + this.parentData.height;
}
};
if(isTouch && featureOptions.touchStrategy == 'disable'){return;}
function Sticky(dom) {
uid++;
stickys++;
this.evtid = '.wsstickyid' + uid;
this.$el = $(dom).data('wsSticky', this);
this.isTable = this.$el.is('thead, tbody, tfoot');
this.$parent = this.$el.parent();
this.elStyle = dom.style;
this.ankered = '';
this.isSticky = false;
this.$placeholder = null;
this.stickyData = {inline: {}};
this.parentData = {};
this.getParentData = this.getCommonParentData;
this.addEvents();
this.update(true);
}
$.extend(Sticky.prototype, stickyMixin, {
addEvents: function () {
var that = this;
this.commonAddEvents();
$window
.on('scroll' + this.evtid, function () {
if (!that.disabled && that.ankered && that.$el[0].offsetWidth) {
that.updatePos();
}
})
;
},
getStickyData: function(){
this.stickyData.top = this.$el.offset().top;
this.getCommonStickyData();
},
updateDimension: function(fromPos){
if(this.isSticky){
this.removeSticky();
}
this.getParentData();
this.getStickyData();
if (this.ankered == 'bottom') {
this.viewportBottomAnker = $window.height() - this.position.bottom;
}
if(!fromPos && this.ankered){
this.updatePos(true);
}
},
updatePos: function(fromDimension){
var offset, shouldSticky, shouldMoveWith;
var scroll = getWinScroll();
if (this.ankered == 'top') {
offset = scroll + this.position.top;
if(this.stickyData.scrollTop < offset && scroll - 9 <= this.parentData.bottom){
shouldMoveWith = ((offset + this.stickyData.outerHeight) - this.parentData.bottom) * -1;
shouldSticky = true;
}
} else if (this.ankered == 'bottom') {
offset = scroll + this.viewportBottomAnker;
if(this.stickyData.bottom > offset &&
offset + 9 >= this.parentData.top){
shouldSticky = true;
shouldMoveWith = offset - this.parentData.top - this.stickyData.outerHeight;
}
}
if (shouldSticky) {
if (!this.isSticky) {
//updateDimension before layout trashing
if(!fromDimension){
this.updateDimension(true);
}
this.setSticky();
}
if(shouldMoveWith < 0){
if(this.ankered == 'top'){
this.elStyle.top = this.position.top + shouldMoveWith +'px';
} else if(this.ankered == 'bottom'){
this.elStyle.bottom = this.position.bottom + shouldMoveWith +'px';
}
}
} else if (this.isSticky) {
this.removeSticky();
}
}
});
function StickyParent(dom) {
uid++;
stickys++;
this.evtid = '.wsstickyid' + uid;
this.$el = $(dom).data('wsSticky', this);
this.isTable = this.$el.is('thead, tbody, tfoot');
this.$parent = this.$el.parent();
this.elStyle = dom.style;
this.ankered = '';
this.isSticky = false;
this.$placeholder = null;
this.stickyData = {inline: {}};
this.parentData = {};
if(this.$parent.css('position') == 'static'){
this.$parent.css('position', 'relative');
}
this.updatePos2 = this.updatePos2.bind(this);
this.addEvents();
this.update(true);
}
$.extend(StickyParent.prototype, stickyMixin, {
addEvents: function () {
var that = this;
this.commonAddEvents();
this.$parent
.on('scroll' + this.evtid, function () {
if (that.ankered && that.$el[0].offsetWidth) {
that.updatePos();
}
})
;
},
getStickyData: function(){
this.stickyData.top = this.$el[0].offsetTop;
this.getCommonStickyData();
},
getParentData: function(){
this.getCommonParentData();
this.parentData.offsetBottom = this.parentData.top + this.$parent.outerHeight();
},
updateDimension: function(fromPos){
var add;
if(this.isSticky){
this.removeSticky();
}
this.getParentData();
this.getStickyData();
this.viewport = $window.height();
if(this.ankered == 'top'){
add = Math.abs(this.position.top) + 9;
} else if(this.ankered == 'bottom') {
add = Math.abs(this.position.bottom) + 9;
this.viewportBottomAnker = this.viewport - this.parentData.bottom;
this.compareBottom = this.stickyData.bottom + this.position.bottom;
this.addBottom = (this.parentData.bottom - this.parentData.paddingTop) - this.viewport;
}
this.viewPortMax = this.parentData.offsetBottom + add + 10;
this.viewPortMin = this.parentData.offsetTop - add - this.viewport;
if(!fromPos){
this.updatePos(true);
}
},
updatePos: function(fromDimension){
var offset, shouldSticky;
var scroll = this.$parent[0].scrollTop;
if (this.ankered == 'top') {
offset = scroll + this.position.top ;
if(this.stickyData.scrollTop - this.parentData.paddingTop < offset){
shouldSticky = true;
}
} else if (this.ankered == 'bottom') {
if(scroll + this.parentData.height < this.compareBottom){
shouldSticky = true;
}
}
if (shouldSticky) {
if (!this.isSticky) {
//updateDimension before layout trashing
if(!fromDimension){
this.updateDimension(true);
}
this.setSticky();
$window
.off('scroll' + this.evtid, this.updatePos2)
.on('scroll' + this.evtid, this.updatePos2)
;
this.updatePos2(true);
}
} else if (this.isSticky) {
this.removeSticky();
$window.off('scroll' + this.evtid, this.updatePos2);
}
},
updatePos2: function(init){
var scrollTop = getWinScroll();
if(init === true || (this.viewPortMax > scrollTop && scrollTop > this.viewPortMin)){
if(this.ankered == 'top'){
if(init === true || (this.viewPortMax > scrollTop && scrollTop > this.viewPortMin)){
this.elStyle.top = this.position.top + this.parentData.top - scrollTop +'px';
}
} else if(this.ankered == 'bottom'){
this.elStyle.bottom = this.position.bottom + (scrollTop - this.addBottom) +'px';
}
}
}
});
var loadDomSupport = function(){
loadDomSupport = $.noop;
webshim.ready('WINDOWLOAD', function(){
webshim.loader.loadList(['dom-extend']);
webshim.ready('dom-extend', function(){
webshim.addShadowDom();
});
});
};
var addSticky = function(){
var stickyData = $.data(this, 'wsSticky');
if(!stickyData){
var $parent = $(this).parent();
$(this).addClass('ws-sticky');
if(($parent.css('overflowY') || $parent.css('overflow') || 'visible') == 'visible'){
new Sticky(this);
} else {
//webshim.warn('currently not supported');
new StickyParent(this);
}
loadDomSupport();
} else if(stickyData.disable) {
stickyData.disable(false);
}
};
if (!support.sticky && support.fixed) {
var selectors = {};
var createUpdateDomSearch = function(media, sels){
var i, created, elems;
var updated = [];
if(!selectors[media]){
selectors[media] = {sels: {}, string: '',
fn: function(context, insertedElement){
var elems = $(selectors[media].string, context).add(insertedElement.filter(selectors[media].string));
if(media){
elems.data('stickymedia', media);
}
elems.each(addSticky);
}
};
created = true;
}
for(i = 0; i < sels.length; i++){
if(!selectors[media].sels[sels[i]]){
selectors[media].sels[sels[i]] = true;
updated.push(sels[i]);
}
}
if(!created && !updated.length){return;}
selectors[media].string = Object.keys(selectors[media].sels).join(', ');
if(created){
$(function(){
webshim.addReady(selectors[media].fn);
});
} else if($.isReady){
elems = $(updated.join(', '));
if(media){
elems.data('stickymedia', media);
}
elems.each(addSticky);
}
};
createUpdateDomSearch('', ['.ws-sticky']);
$(function(){
$(document).on('wssticky', function(e){
addSticky.call(e.target);
});
});
if(featureOptions.parseCSS){
if(window.Polyfill && Polyfill.prototype && Polyfill.prototype.doMatched){
var onEnableRule = function(rule){
var curSelectors = rule.getSelectors().split(/\,\s*/g);
var media = (!rule._rule.media || !rule._rule.media.length) ? '' : rule.getMedia();
createUpdateDomSearch(media || '', curSelectors);
};
Polyfill({declarations:["position:sticky"]})
.doMatched(function(rules){
rules.each(onEnableRule);
})
;
} else {
webshim.warn('Polyfill for CSS polyfilling made easy has to be included');
}
}
}
if(document.readyState == 'complete'){
webshim.isReady('WINDOWLOAD', true);
}
});