/* 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); }