| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945 |
- // exports
- FC.intersectRanges = intersectRanges;
- FC.applyAll = applyAll;
- FC.debounce = debounce;
- FC.isInt = isInt;
- FC.htmlEscape = htmlEscape;
- FC.cssToStr = cssToStr;
- FC.proxy = proxy;
- FC.capitaliseFirstLetter = capitaliseFirstLetter;
- /* 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': ''
- });
- }
- // Make the mouse cursor express that an event is not allowed in the current area
- function disableCursor() {
- $('body').addClass('fc-not-allowed');
- }
- // Returns the mouse cursor to its original look
- function enableCursor() {
- $('body').removeClass('fc-not-allowed');
- }
- // 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).height(newHeight);
- }
- });
- }
- // Undoes distrubuteHeight, restoring all els to their natural height
- function undistributeHeight(els) {
- els.height('');
- }
- // Given `els`, a jQuery set of <td> 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('> span').each(function(i, innerEl) {
- var innerWidth = $(innerEl).outerWidth();
- if (innerWidth > maxInnerWidth) {
- maxInnerWidth = innerWidth;
- }
- });
- maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance
- els.width(maxInnerWidth);
- return maxInnerWidth;
- }
- // Given one element that resides inside another,
- // Subtracts the height of the inner element from the outer element.
- function subtractInnerElHeight(outerEl, innerEl) {
- var both = outerEl.add(innerEl);
- var diff;
- // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
- both.css({
- position: 'relative', // cause a reflow, which will force fresh dimension recalculation
- left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
- });
- diff = outerEl.outerHeight() - innerEl.outerHeight(); // grab the dimensions
- both.css({ position: '', left: '' }); // undo hack
- return diff;
- }
- /* Element Geom Utilities
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.getOuterRect = getOuterRect;
- FC.getClientRect = getClientRect;
- FC.getContentRect = getContentRect;
- FC.getScrollbarWidths = getScrollbarWidths;
- // borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51
- function getScrollParent(el) {
- var position = el.css('position'),
- scrollParent = el.parents().filter(function() {
- var parent = $(this);
- return (/(auto|scroll)/).test(
- parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')
- );
- }).eq(0);
- return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent;
- }
- // Queries the outer bounding area of a jQuery element.
- // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
- // Origin is optional.
- function getOuterRect(el, origin) {
- var offset = el.offset();
- var left = offset.left - (origin ? origin.left : 0);
- var top = offset.top - (origin ? origin.top : 0);
- return {
- left: left,
- right: left + el.outerWidth(),
- top: top,
- bottom: top + el.outerHeight()
- };
- }
- // Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.
- // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
- // Origin is optional.
- // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
- function getClientRect(el, origin) {
- var offset = el.offset();
- var scrollbarWidths = getScrollbarWidths(el);
- var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left - (origin ? origin.left : 0);
- var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top - (origin ? origin.top : 0);
- return {
- left: left,
- right: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars
- top: top,
- bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars
- };
- }
- // Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.
- // Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
- // Origin is optional.
- function getContentRect(el, origin) {
- var offset = el.offset(); // just outside of border, margin not included
- var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left') -
- (origin ? origin.left : 0);
- var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top') -
- (origin ? origin.top : 0);
- return {
- left: left,
- right: left + el.width(),
- top: top,
- bottom: top + el.height()
- };
- }
- // Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element.
- // NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
- function getScrollbarWidths(el) {
- var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars
- var widths = {
- left: 0,
- right: 0,
- top: 0,
- bottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar
- };
- if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?
- widths.left = leftRightWidth;
- }
- else {
- widths.right = leftRightWidth;
- }
- return widths;
- }
- // Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side
- var _isLeftRtlScrollbars = null;
- function getIsLeftRtlScrollbars() { // responsible for caching the computation
- if (_isLeftRtlScrollbars === null) {
- _isLeftRtlScrollbars = computeIsLeftRtlScrollbars();
- }
- return _isLeftRtlScrollbars;
- }
- function computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it
- var el = $('<div><div/></div>')
- .css({
- position: 'absolute',
- top: -1000,
- left: 0,
- border: 0,
- padding: 0,
- overflow: 'scroll',
- direction: 'rtl'
- })
- .appendTo('body');
- var innerEl = el.children();
- var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar?
- el.remove();
- return res;
- }
- // Retrieves a jQuery element's computed CSS value as a floating-point number.
- // If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero.
- function getCssFloat(el, prop) {
- return parseFloat(el.css(prop)) || 0;
- }
- /* Mouse / Touch Utilities
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.preventDefault = preventDefault;
- // 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;
- }
- function getEvX(ev) {
- if (ev.pageX !== undefined) {
- return ev.pageX;
- }
- var touches = ev.originalEvent.touches;
- if (touches) {
- return touches[0].pageX;
- }
- }
- function getEvY(ev) {
- if (ev.pageY !== undefined) {
- return ev.pageY;
- }
- var touches = ev.originalEvent.touches;
- if (touches) {
- return touches[0].pageY;
- }
- }
- function getEvIsTouch(ev) {
- return /^touch/.test(ev.type);
- }
- function preventSelection(el) {
- el.addClass('fc-unselectable')
- .on('selectstart', preventDefault);
- }
- // Stops a mouse/touch event from doing it's native browser action
- function preventDefault(ev) {
- ev.preventDefault();
- }
- // attach a handler to get called when ANY scroll action happens on the page.
- // this was impossible to do with normal on/off because 'scroll' doesn't bubble.
- // http://stackoverflow.com/a/32954565/96342
- // returns `true` on success.
- function bindAnyScroll(handler) {
- if (window.addEventListener) {
- window.addEventListener('scroll', handler, true); // useCapture=true
- return true;
- }
- return false;
- }
- // undoes bindAnyScroll. must pass in the original function.
- // returns `true` on success.
- function unbindAnyScroll(handler) {
- if (window.removeEventListener) {
- window.removeEventListener('scroll', handler, true); // useCapture=true
- return true;
- }
- return false;
- }
- /* General Geometry Utils
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.intersectRects = intersectRects;
- // Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
- function intersectRects(rect1, rect2) {
- var res = {
- left: Math.max(rect1.left, rect2.left),
- right: Math.min(rect1.right, rect2.right),
- top: Math.max(rect1.top, rect2.top),
- bottom: Math.min(rect1.bottom, rect2.bottom)
- };
- if (res.left < res.right && res.top < res.bottom) {
- return res;
- }
- return false;
- }
- // Returns a new point that will have been moved to reside within the given rectangle
- function constrainPoint(point, rect) {
- return {
- left: Math.min(Math.max(point.left, rect.left), rect.right),
- top: Math.min(Math.max(point.top, rect.top), rect.bottom)
- };
- }
- // Returns a point that is the center of the given rectangle
- function getRectCenter(rect) {
- return {
- left: (rect.left + rect.right) / 2,
- top: (rect.top + rect.bottom) / 2
- };
- }
- // Subtracts point2's coordinates from point1's coordinates, returning a delta
- function diffPoints(point1, point2) {
- return {
- left: point1.left - point2.left,
- top: point1.top - point2.top
- };
- }
- /* Object Ordering by Field
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.parseFieldSpecs = parseFieldSpecs;
- FC.compareByFieldSpecs = compareByFieldSpecs;
- FC.compareByFieldSpec = compareByFieldSpec;
- FC.flexibleCompare = flexibleCompare;
- function parseFieldSpecs(input) {
- var specs = [];
- var tokens = [];
- var i, token;
- if (typeof input === 'string') {
- tokens = input.split(/\s*,\s*/);
- }
- else if (typeof input === 'function') {
- tokens = [ input ];
- }
- else if ($.isArray(input)) {
- tokens = input;
- }
- for (i = 0; i < tokens.length; i++) {
- token = tokens[i];
- if (typeof token === 'string') {
- specs.push(
- token.charAt(0) == '-' ?
- { field: token.substring(1), order: -1 } :
- { field: token, order: 1 }
- );
- }
- else if (typeof token === 'function') {
- specs.push({ func: token });
- }
- }
- return specs;
- }
- function compareByFieldSpecs(obj1, obj2, fieldSpecs) {
- var i;
- var cmp;
- for (i = 0; i < fieldSpecs.length; i++) {
- cmp = compareByFieldSpec(obj1, obj2, fieldSpecs[i]);
- if (cmp) {
- return cmp;
- }
- }
- return 0;
- }
- function compareByFieldSpec(obj1, obj2, fieldSpec) {
- if (fieldSpec.func) {
- return fieldSpec.func(obj1, obj2);
- }
- return flexibleCompare(obj1[fieldSpec.field], obj2[fieldSpec.field]) *
- (fieldSpec.order || 1);
- }
- function flexibleCompare(a, b) {
- if (!a && !b) {
- return 0;
- }
- if (b == null) {
- return -1;
- }
- if (a == null) {
- return 1;
- }
- if ($.type(a) === 'string' || $.type(b) === 'string') {
- return String(a).localeCompare(String(b));
- }
- return a - b;
- }
- /* FullCalendar-specific Misc Utilities
- ----------------------------------------------------------------------------------------------------------------------*/
- // Computes the intersection of the two ranges. Returns undefined if no intersection.
- // Expects all dates to be normalized to the same timezone beforehand.
- // TODO: move to date section?
- function intersectRanges(subjectRange, constraintRange) {
- var subjectStart = subjectRange.start;
- var subjectEnd = subjectRange.end;
- var constraintStart = constraintRange.start;
- var constraintEnd = constraintRange.end;
- var segStart, segEnd;
- var isStart, isEnd;
- if (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all?
- if (subjectStart >= constraintStart) {
- segStart = subjectStart.clone();
- isStart = true;
- }
- else {
- segStart = constraintStart.clone();
- isStart = false;
- }
- if (subjectEnd <= constraintEnd) {
- segEnd = subjectEnd.clone();
- isEnd = true;
- }
- else {
- segEnd = constraintEnd.clone();
- isEnd = false;
- }
- return {
- start: segStart,
- end: segEnd,
- isStart: isStart,
- isEnd: isEnd
- };
- }
- }
- /* Date Utilities
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.computeIntervalUnit = computeIntervalUnit;
- FC.divideRangeByDuration = divideRangeByDuration;
- FC.divideDurationByDuration = divideDurationByDuration;
- FC.multiplyDuration = multiplyDuration;
- FC.durationHasTime = durationHasTime;
- var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
- var intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ];
- // Diffs the two moments into a Duration where full-days are recorded first, then the remaining time.
- // Moments will have their timezones normalized.
- function diffDayTime(a, b) {
- return moment.duration({
- days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),
- ms: a.time() - b.time() // time-of-day from day start. disregards timezone
- });
- }
- // Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations.
- function diffDay(a, b) {
- return moment.duration({
- days: a.clone().stripTime().diff(b.clone().stripTime(), 'days')
- });
- }
- // Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding.
- function diffByUnit(a, b, unit) {
- return moment.duration(
- Math.round(a.diff(b, unit, true)), // returnFloat=true
- unit
- );
- }
- // Computes the unit name of the largest whole-unit period of time.
- // For example, 48 hours will be "days" whereas 49 hours will be "hours".
- // Accepts start/end, a range object, or an original duration object.
- function computeIntervalUnit(start, end) {
- var i, unit;
- var val;
- for (i = 0; i < intervalUnits.length; i++) {
- unit = intervalUnits[i];
- val = computeRangeAs(unit, start, end);
- if (val >= 1 && isInt(val)) {
- break;
- }
- }
- return unit; // will be "milliseconds" if nothing else matches
- }
- // Computes the number of units (like "hours") in the given range.
- // Range can be a {start,end} object, separate start/end args, or a Duration.
- // Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling
- // of month-diffing logic (which tends to vary from version to version).
- function computeRangeAs(unit, start, end) {
- if (end != null) { // given start, end
- return end.diff(start, unit, true);
- }
- else if (moment.isDuration(start)) { // given duration
- return start.as(unit);
- }
- else { // given { start, end } range object
- return start.end.diff(start.start, unit, true);
- }
- }
- // Intelligently divides a range (specified by a start/end params) by a duration
- function divideRangeByDuration(start, end, dur) {
- var months;
- if (durationHasTime(dur)) {
- return (end - start) / dur;
- }
- months = dur.asMonths();
- if (Math.abs(months) >= 1 && isInt(months)) {
- return end.diff(start, 'months', true) / months;
- }
- return end.diff(start, 'days', true) / dur.asDays();
- }
- // Intelligently divides one duration by another
- function divideDurationByDuration(dur1, dur2) {
- var months1, months2;
- if (durationHasTime(dur1) || durationHasTime(dur2)) {
- return dur1 / dur2;
- }
- months1 = dur1.asMonths();
- months2 = dur2.asMonths();
- if (
- Math.abs(months1) >= 1 && isInt(months1) &&
- Math.abs(months2) >= 1 && isInt(months2)
- ) {
- return months1 / months2;
- }
- return dur1.asDays() / dur2.asDays();
- }
- // Intelligently multiplies a duration by a number
- function multiplyDuration(dur, n) {
- var months;
- if (durationHasTime(dur)) {
- return moment.duration(dur * n);
- }
- months = dur.asMonths();
- if (Math.abs(months) >= 1 && isInt(months)) {
- return moment.duration({ months: months * n });
- }
- return moment.duration({ days: dur.asDays() * n });
- }
- // Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)
- function durationHasTime(dur) {
- return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());
- }
- function isNativeDate(input) {
- return Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
- }
- // Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00"
- function isTimeString(str) {
- return /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str);
- }
- /* Logging and Debug
- ----------------------------------------------------------------------------------------------------------------------*/
- FC.log = function() {
- var console = window.console;
- if (console && console.log) {
- return console.log.apply(console, arguments);
- }
- };
- FC.warn = function() {
- var console = window.console;
- if (console && console.warn) {
- return console.warn.apply(console, arguments);
- }
- else {
- return FC.log.apply(FC, arguments);
- }
- };
- /* General Utilities
- ----------------------------------------------------------------------------------------------------------------------*/
- var hasOwnPropMethod = {}.hasOwnProperty;
- // Merges an array of objects into a single object.
- // The second argument allows for an array of property names who's object values will be merged together.
- function mergeProps(propObjs, complexProps) {
- var dest = {};
- var i, name;
- var complexObjs;
- var j, val;
- var props;
- if (complexProps) {
- for (i = 0; i < complexProps.length; i++) {
- name = complexProps[i];
- complexObjs = [];
- // collect the trailing object values, stopping when a non-object is discovered
- for (j = propObjs.length - 1; j >= 0; j--) {
- val = propObjs[j][name];
- if (typeof val === 'object') {
- complexObjs.unshift(val);
- }
- else if (val !== undefined) {
- dest[name] = val; // if there were no objects, this value will be used
- break;
- }
- }
- // if the trailing values were objects, use the merged value
- if (complexObjs.length) {
- dest[name] = mergeProps(complexObjs);
- }
- }
- }
- // copy values into the destination, going from last to first
- for (i = propObjs.length - 1; i >= 0; i--) {
- props = propObjs[i];
- for (name in props) {
- if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
- dest[name] = props[name];
- }
- }
- }
- return dest;
- }
- // Create an object that has the given prototype. Just like Object.create
- function createObject(proto) {
- var f = function() {};
- f.prototype = proto;
- return new f();
- }
- function copyOwnProps(src, dest) {
- for (var name in src) {
- if (hasOwnProp(src, name)) {
- dest[name] = src[name];
- }
- }
- }
- // Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:
- // https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
- function copyNativeMethods(src, dest) {
- var names = [ 'constructor', 'toString', 'valueOf' ];
- var i, name;
- for (i = 0; i < names.length; i++) {
- name = names[i];
- if (src[name] !== Object.prototype[name]) {
- dest[name] = src[name];
- }
- }
- }
- function hasOwnProp(obj, name) {
- return hasOwnPropMethod.call(obj, name);
- }
- // Is the given value a non-object non-function value?
- function isAtomic(val) {
- return /undefined|null|boolean|number|string/.test($.type(val));
- }
- function applyAll(functions, thisObj, args) {
- if ($.isFunction(functions)) {
- functions = [ functions ];
- }
- if (functions) {
- var i;
- var ret;
- for (i=0; i<functions.length; i++) {
- ret = functions[i].apply(thisObj, args) || ret;
- }
- return ret;
- }
- }
- function firstDefined() {
- for (var i=0; i<arguments.length; i++) {
- if (arguments[i] !== undefined) {
- return arguments[i];
- }
- }
- }
- function htmlEscape(s) {
- return (s + '').replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/'/g, ''')
- .replace(/"/g, '"')
- .replace(/\n/g, '<br />');
- }
- function stripHtmlEntities(text) {
- return text.replace(/&.*?;/g, '');
- }
- // Given a hash of CSS properties, returns a string of CSS.
- // Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values.
- function cssToStr(cssProps) {
- var statements = [];
- $.each(cssProps, function(name, val) {
- if (val != null) {
- statements.push(name + ':' + val);
- }
- });
- return statements.join(';');
- }
- function capitaliseFirstLetter(str) {
- return str.charAt(0).toUpperCase() + str.slice(1);
- }
- function compareNumbers(a, b) { // for .sort()
- return a - b;
- }
- function isInt(n) {
- return n % 1 === 0;
- }
- // Returns a method bound to the given object context.
- // Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with
- // different contexts as identical when binding/unbinding events.
- function proxy(obj, methodName) {
- var method = obj[methodName];
- return function() {
- return method.apply(obj, arguments);
- };
- }
- // Returns a function, that, as long as it continues to be invoked, will not
- // be triggered. The function will be called after it stops being called for
- // N milliseconds. If `immediate` is passed, trigger the function on the
- // leading edge, instead of the trailing.
- // https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714
- function debounce(func, wait, immediate) {
- var timeout, args, context, timestamp, result;
- var later = function() {
- var last = +new Date() - timestamp;
- if (last < wait) {
- timeout = setTimeout(later, wait - last);
- }
- else {
- timeout = null;
- if (!immediate) {
- result = func.apply(context, args);
- context = args = null;
- }
- }
- };
- return function() {
- context = this;
- args = arguments;
- timestamp = +new Date();
- var callNow = immediate && !timeout;
- if (!timeout) {
- timeout = setTimeout(later, wait);
- }
- if (callNow) {
- result = func.apply(context, args);
- context = args = null;
- }
- return result;
- };
- }
- // HACK around jQuery's now A+ promises: execute callback synchronously if already resolved.
- // thenFunc shouldn't accept args.
- // similar to whenResources in Scheduler plugin.
- function syncThen(promise, thenFunc) {
- // not a promise, or an already-resolved promise?
- if (!promise || !promise.then || promise.state() === 'resolved') {
- return $.when(thenFunc()); // resolve immediately
- }
- else if (thenFunc) {
- return promise.then(thenFunc);
- }
- }
|