/* FullCalendar-specific DOM Utilities
----------------------------------------------------------------------------------------------------------------------*/
// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left
// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that.
function compensateScroll(rowEls, scrollbarWidths) {
if (scrollbarWidths.left) {
rowEls.css({
'border-left-width': 1,
'margin-left': scrollbarWidths.left - 1
});
}
if (scrollbarWidths.right) {
rowEls.css({
'border-right-width': 1,
'margin-right': scrollbarWidths.right - 1
});
}
}
// Undoes compensateScroll and restores all borders/margins
function uncompensateScroll(rowEls) {
rowEls.css({
'margin-left': '',
'margin-right': '',
'border-left-width': '',
'border-right-width': ''
});
}
// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate.
// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering
// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and
// reduces the available height.
function distributeHeight(els, availableHeight, shouldRedistribute) {
// *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions,
// and it is better to be shorter than taller, to avoid creating unnecessary scrollbars.
var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element
var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE*
var flexEls = []; // elements that are allowed to expand. array of DOM nodes
var flexOffsets = []; // amount of vertical space it takes up
var flexHeights = []; // actual css height
var usedHeight = 0;
undistributeHeight(els); // give all elements their natural height
// find elements that are below the recommended height (expandable).
// important to query for heights in a single first pass (to avoid reflow oscillation).
els.each(function(i, el) {
var minOffset = i === els.length - 1 ? minOffset2 : minOffset1;
var naturalOffset = $(el).outerHeight(true);
if (naturalOffset < minOffset) {
flexEls.push(el);
flexOffsets.push(naturalOffset);
flexHeights.push($(el).height());
}
else {
// this element stretches past recommended height (non-expandable). mark the space as occupied.
usedHeight += naturalOffset;
}
});
// readjust the recommended height to only consider the height available to non-maxed-out rows.
if (shouldRedistribute) {
availableHeight -= usedHeight;
minOffset1 = Math.floor(availableHeight / flexEls.length);
minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE*
}
// assign heights to all expandable elements
$(flexEls).each(function(i, el) {
var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1;
var naturalOffset = flexOffsets[i];
var naturalHeight = flexHeights[i];
var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding
if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things
$(el).css('min-height', newHeight);
}
});
}
// Undoes distrubuteHeight, restoring all els to their natural height
function undistributeHeight(els) {
els.css('min-height', '');
}
// Given `els`, a jQuery set of
cells, find the cell with the largest natural width and set the widths of all the
// cells to be that width.
// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline
function matchCellWidths(els) {
var maxInnerWidth = 0;
els.find('> *').each(function(i, innerEl) {
var innerWidth = $(innerEl).outerWidth();
if (innerWidth > maxInnerWidth) {
maxInnerWidth = innerWidth;
}
});
els.width(maxInnerWidth);
return maxInnerWidth;
}
/* General DOM Utilities
----------------------------------------------------------------------------------------------------------------------*/
// Given a container element, return an object with the pixel values of the left/right scrollbars.
// Left scrollbars might occur on RTL browsers (IE maybe?) but I have not tested.
// PREREQUISITE: container element must have a single child with display:block
function getScrollbarWidths(container) {
var containerLeft = container.offset().left;
var containerRight = containerLeft + container.width();
var inner = container.children();
var innerLeft = inner.offset().left;
var innerRight = innerLeft + inner.outerWidth();
return {
left: innerLeft - containerLeft,
right: containerRight - innerRight
};
}
// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
function isPrimaryMouseButton(ev) {
return ev.which == 1 && !ev.ctrlKey;
}
/* FullCalendar-specific Misc Utilities
----------------------------------------------------------------------------------------------------------------------*/
function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
obj = obj || {};
if (obj[name] !== undefined) {
return obj[name];
}
var parts = name.split(/(?=[A-Z])/),
i = parts.length - 1, res;
for (; i>=0; i--) {
res = obj[parts[i].toLowerCase()];
if (res !== undefined) {
return res;
}
}
return obj['default'];
}
/* Date Utilities
----------------------------------------------------------------------------------------------------------------------*/
var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
// diffs the two moments into a Duration where full-days are recorded first, then the remaining time.
function dayishDiff(a, b) {
return moment.duration({
days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),
ms: a.time() - b.time()
});
}
function isNativeDate(input) {
return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
}
function dateCompare(a, b) { // works with Moments and native Dates
return a - b;
}
/* General Utilities
----------------------------------------------------------------------------------------------------------------------*/
fc.applyAll = applyAll; // export
// Create an object that has the given prototype. Just like Object.create
function createObject(proto) {
var f = function() {};
f.prototype = proto;
return new f();
}
// Copies specifically-owned (non-protoype) properties of `b` onto `a`.
// FYI, $.extend would copy *all* properties of `b` onto `a`.
function extend(a, b) {
for (var i in b) {
if (b.hasOwnProperty(i)) {
a[i] = b[i];
}
}
}
function flattenArray(a) { // flatten an array of arrays, one level deep
return Array.prototype.concat.apply([], a);
}
function applyAll(functions, thisObj, args) {
if ($.isFunction(functions)) {
functions = [ functions ];
}
if (functions) {
var i;
var ret;
for (i=0; i/g, '>')
.replace(/'/g, ''')
.replace(/"/g, '"')
.replace(/\n/g, ' ');
}
function stripHTMLEntities(text) {
return text.replace(/&.*?;/g, '');
}
function capitaliseFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
|